Browse Source

Merge pull request #8371 from nocodb/nc-fix/sf-issues

fix: pre-post insert ops
pull/8377/head
Pranav C 7 months ago committed by GitHub
parent
commit
2197b8db1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 15
      packages/nc-gui/components/smartsheet/Cell.vue
  2. 19
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  3. 36
      packages/nc-gui/components/smartsheet/grid/Table.vue
  4. 16
      packages/nc-gui/components/virtual-cell/components/UnLinkedItems.vue
  5. 66
      packages/nc-gui/composables/useData.ts
  6. 4
      packages/nc-gui/composables/useExpandedFormStore.ts
  7. 6
      packages/nc-gui/composables/useLTARStore.ts
  8. 1
      packages/nc-gui/lib/types.ts
  9. 10
      packages/nc-gui/utils/dataUtils.ts
  10. 1
      packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts
  11. 144
      packages/nocodb/src/db/BaseModelSqlv2.ts
  12. 1
      packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaSnowflake.ts
  13. 7
      packages/nocodb/src/helpers/getUniqueName.ts
  14. 15
      packages/nocodb/src/helpers/populateMeta.ts

15
packages/nc-gui/components/smartsheet/Cell.vue

@ -112,6 +112,17 @@ const syncValue = useDebounceFn(
{ maxWait: 2000 },
)
let saveTimer: number
const updateWhenEditCompleted = () => {
if (editEnabled.value) {
if (saveTimer) clearTimeout(saveTimer)
saveTimer = window.setTimeout(updateWhenEditCompleted, 500)
} else {
emit('save')
}
}
const vModel = computed({
get: () => {
return props.modelValue
@ -122,7 +133,9 @@ const vModel = computed({
} else if (val !== props.modelValue) {
currentRow.value.rowMeta.changed = true
emit('update:modelValue', val)
if (isAutoSaved(column.value)) {
if (column.value.pk) {
updateWhenEditCompleted()
} else if (isAutoSaved(column.value)) {
syncValue()
} else if (!isManualSaved(column.value)) {
emit('save')

19
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -51,6 +51,7 @@ interface Props {
lastRow?: boolean
closeAfterSave?: boolean
newRecordHeader?: string
skipReload?: boolean
}
const props = defineProps<Props>()
@ -102,7 +103,7 @@ const expandedFormScrollWrapper = ref()
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
const reloadViewDataTrigger = inject(ReloadViewDataHookInj)
const reloadViewDataTrigger = inject(ReloadViewDataHookInj, createEventHook())
const { addOrEditStackRow } = useKanbanViewStoreOrThrow()
@ -137,6 +138,8 @@ provide(MetaInj, meta)
const isLoading = ref(true)
const isSaving = ref(false)
const {
commentsDrawer,
changedColumns,
@ -207,26 +210,31 @@ const onDuplicateRow = () => {
}
const save = async () => {
isSaving.value = true
let kanbanClbk
if (activeView.value?.type === ViewTypes.KANBAN) {
kanbanClbk = (row: any, isNewRow: boolean) => {
addOrEditStackRow(row, isNewRow)
}
}
if (isNew.value) {
await _save(rowState.value, undefined, {
kanbanClbk,
})
reloadTrigger?.trigger()
reloadViewDataTrigger?.trigger()
} else {
await _save(undefined, undefined, {
kanbanClbk,
})
_loadRow()
}
if (!props.skipReload) {
reloadTrigger?.trigger()
reloadViewDataTrigger?.trigger()
}
isUnsavedFormExist.value = false
if (props.closeAfterSave) {
@ -234,6 +242,8 @@ const save = async () => {
}
emits('createdRecord', _row.value.row)
isSaving.value = false
}
const isPreventChangeModalOpen = ref(false)
@ -871,6 +881,7 @@ export default {
<NcButton
v-e="['c:row-expand:save']"
:disabled="changedColumns.size === 0 && !isUnsavedFormExist"
:loading="isSaving"
class="nc-expand-form-save-btn !xs:(text-base)"
data-testid="nc-expanded-form-save"
type="primary"
@ -917,7 +928,7 @@ export default {
<div class="flex flex-row justify-end gap-x-2 mt-5">
<NcButton type="secondary" @click="discardPreventModal">{{ $t('labels.discard') }}</NcButton>
<NcButton key="submit" type="primary" label="Rename Table" loading-label="Renaming Table" @click="saveChanges">
<NcButton key="submit" type="primary" :loading="isSaving" @click="saveChanges">
{{ $t('tooltip.saveChanges') }}
</NcButton>
</div>

36
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -1064,13 +1064,27 @@ async function resetAndChangePage(row: number, col: number, pageChange?: number)
scrollToCell?.()
}
const saveOrUpdateRecords = async (args: { metaValue?: TableType; viewMetaValue?: ViewType; data?: any } = {}) => {
const temporaryNewRowStore = ref<Row[]>([])
const saveOrUpdateRecords = async (
args: { metaValue?: TableType; viewMetaValue?: ViewType; data?: any; keepNewRecords?: boolean } = {},
) => {
for (const currentRow of args.data || dataRef.value) {
if (currentRow.rowMeta.fromExpandedForm) continue
/** if new record save row and save the LTAR cells */
if (currentRow.rowMeta.new) {
const savedRow = await updateOrSaveRow?.(currentRow, '', {}, args)
await syncLTARRefs?.(currentRow, savedRow, args)
currentRow.rowMeta.changed = false
const beforeSave = clone(currentRow)
const savedRow = await updateOrSaveRow?.(currentRow, '', currentRow.rowMeta.ltarState || {}, args)
if (savedRow) {
currentRow.rowMeta.changed = false
} else {
if (args.keepNewRecords) {
if (beforeSave.rowMeta.new && Object.keys(beforeSave.row).length) {
temporaryNewRowStore.value.push(beforeSave)
}
}
}
continue
}
@ -1277,7 +1291,10 @@ const showFillHandle = computed(
isFormula(fields.value[activeCell.col]) ||
isCreatedOrLastModifiedTimeCol(fields.value[activeCell.col]) ||
isCreatedOrLastModifiedByCol(fields.value[activeCell.col])
),
) &&
!isViewDataLoading.value &&
!isPaginationLoading.value &&
dataRef.value.length,
)
watch(
@ -1329,10 +1346,17 @@ async function reloadViewDataHandler(params: void | { shouldShowLoading?: boolea
predictedNextColumn.value = predictedNextColumn.value.filter((c) => !fieldsAvailable?.includes(c.title))
}
// save any unsaved data before reload
await saveOrUpdateRecords()
await saveOrUpdateRecords({
keepNewRecords: true,
})
await loadData?.({ ...(params?.offset !== undefined ? { offset: params.offset } : {}) })
if (temporaryNewRowStore.value.length) {
dataRef.value.push(...temporaryNewRowStore.value)
temporaryNewRowStore.value = []
}
calculateSlices()
isViewDataLoading.value = false

16
packages/nc-gui/components/virtual-cell/components/UnLinkedItems.vue

@ -65,6 +65,10 @@ const isForm = inject(IsFormInj, ref(false))
const saveRow = inject(SaveRowInj, () => {})
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
const reloadViewDataTrigger = inject(ReloadViewDataHookInj, createEventHook())
const linkRow = async (row: Record<string, any>, id: number) => {
if (isNew.value) {
addLTARRef(row, injectedColumn?.value as ColumnType)
@ -213,6 +217,15 @@ const addNewRecord = () => {
}
const onCreatedRecord = (record: any) => {
addLTARRef(record, injectedColumn?.value as ColumnType)
reloadTrigger?.trigger({
shouldShowLoading: false,
})
reloadViewDataTrigger?.trigger({
shouldShowLoading: false,
})
const msgVNode = h(
'div',
{
@ -240,6 +253,8 @@ const onCreatedRecord = (record: any) => {
)
message.success(msgVNode)
vModel.value = false
}
const linkedShortcuts = (e: KeyboardEvent) => {
@ -458,6 +473,7 @@ const onFilterChange = () => {
:row-id="extractPkFromRow(expandedFormRow, relatedTableMeta.columns as ColumnType[])"
:state="newRowState"
use-meta-fields
:skip-reload="true"
@created-record="onCreatedRecord"
/>
</Suspense>

66
packages/nc-gui/composables/useData.ts

@ -477,8 +477,8 @@ export function useData(args: {
try {
await $api.dbTableRow.nestedAdd(
NOCO,
base.value.title as string,
metaValue?.title as string,
base.value.id as string,
metaValue?.id as string,
encodeURIComponent(rowId),
type as RelationTypes,
column.title as string,
@ -630,23 +630,25 @@ export function useData(args: {
async function deleteSelectedRows() {
let row = formattedData.value.length
let removedRowsData: Record<string, any>[] = []
const removedRowsData: Record<string, any>[] = []
let compositePrimaryKey = ''
while (row--) {
const { row: rowObj, rowMeta } = formattedData.value[row] as Record<string, any>
const { row: rowData, rowMeta } = formattedData.value[row] as Record<string, any>
if (!rowMeta.selected) {
continue
}
if (!rowMeta.new) {
const extractedPk = extractPk(meta?.value?.columns as ColumnType[])
const compositePkValue = extractPkFromRow(rowObj, meta?.value?.columns as ColumnType[])
const compositePkValue = extractPkFromRow(rowData, meta?.value?.columns as ColumnType[])
const pkData = rowPkData(rowData, meta?.value?.columns as ColumnType[])
if (extractedPk && compositePkValue) {
if (!compositePrimaryKey) compositePrimaryKey = extractedPk
removedRowsData.push({
[compositePrimaryKey]: compositePkValue as string,
pkData,
row: clone(formattedData.value[row]) as Row,
rowIndex: row as number,
})
@ -670,20 +672,7 @@ export function useData(args: {
rowObj.row = clone(fullRecord)
}
const removedRowIds: Record<string, any>[] = await bulkDeleteRows(
removedRowsData.map((row) => ({ [compositePrimaryKey]: row[compositePrimaryKey] as string })),
)
if (Array.isArray(removedRowIds)) {
const removedRowsDataSet = new Set(removedRowIds.map((row) => row[compositePrimaryKey]))
removedRowsData = removedRowsData.filter((row) => removedRowsDataSet.has(row[compositePrimaryKey] as string))
const rowIndexesSet = new Set(removedRowsData.map((row) => row.rowIndex))
formattedData.value = formattedData.value.filter((_, index) => rowIndexesSet.has(index))
} else {
removedRowsData = []
}
await bulkDeleteRows(removedRowsData.map((row) => row.pkData))
} catch (e: any) {
return message.error(`${t('msg.error.deleteRowFailed')}: ${await extractSdkResponseErrorMsg(e)}`)
}
@ -692,10 +681,8 @@ export function useData(args: {
addUndo({
redo: {
fn: async function redo(this: UndoRedoAction, removedRowsData: Record<string, any>[], compositePrimaryKey: string) {
const removedRowIds = await bulkDeleteRows(
removedRowsData.map((row) => ({ [compositePrimaryKey]: row[compositePrimaryKey] as string })),
)
fn: async function redo(this: UndoRedoAction, removedRowsData: Record<string, any>[]) {
const removedRowIds = await bulkDeleteRows(removedRowsData.map((row) => row.pkData))
if (Array.isArray(removedRowIds)) {
for (const { row } of removedRowsData) {
@ -708,7 +695,7 @@ export function useData(args: {
await callbacks?.syncPagination?.()
},
args: [removedRowsData, compositePrimaryKey],
args: [removedRowsData],
},
undo: {
fn: async function undo(
@ -764,22 +751,24 @@ export function useData(args: {
// plus one because we want to include the end row
let row = start + 1
let removedRowsData: Record<string, any>[] = []
const removedRowsData: Record<string, any>[] = []
let compositePrimaryKey = ''
while (row--) {
try {
const { row: rowObj, rowMeta } = formattedData.value[row] as Record<string, any>
const { row: rowData, rowMeta } = formattedData.value[row] as Record<string, any>
if (!rowMeta.new) {
const extractedPk = extractPk(meta?.value?.columns as ColumnType[])
const compositePkValue = extractPkFromRow(rowObj, meta?.value?.columns as ColumnType[])
const compositePkValue = extractPkFromRow(rowData, meta?.value?.columns as ColumnType[])
const pkData = rowPkData(rowData, meta?.value?.columns as ColumnType[])
if (extractedPk && compositePkValue) {
if (!compositePrimaryKey) compositePrimaryKey = extractedPk
removedRowsData.push({
[compositePrimaryKey]: compositePkValue as string,
pkData,
row: clone(formattedData.value[row]) as Row,
rowIndex: row as number,
})
@ -808,20 +797,7 @@ export function useData(args: {
rowObj.row = clone(fullRecord)
}
const removedRowIds: Record<string, any>[] = await bulkDeleteRows(
removedRowsData.map((row) => ({ [compositePrimaryKey]: row[compositePrimaryKey] as string })),
)
if (Array.isArray(removedRowIds)) {
const removedRowsDataSet = new Set(removedRowIds.map((row) => row[compositePrimaryKey]))
removedRowsData = removedRowsData.filter((row) => removedRowsDataSet.has(row[compositePrimaryKey] as string))
const rowIndexesSet = new Set(removedRowsData.map((row) => row.rowIndex))
formattedData.value = formattedData.value.filter((_, index) => rowIndexesSet.has(index))
} else {
removedRowsData = []
}
await bulkDeleteRows(removedRowsData.map((row) => row.pkData))
} catch (e: any) {
return message.error(`${t('msg.error.deleteRowFailed')}: ${await extractSdkResponseErrorMsg(e)}`)
}
@ -830,10 +806,8 @@ export function useData(args: {
addUndo({
redo: {
fn: async function redo(this: UndoRedoAction, removedRowsData: Record<string, any>[], compositePrimaryKey: string) {
const removedRowIds = await bulkDeleteRows(
removedRowsData.map((row) => ({ [compositePrimaryKey]: row[compositePrimaryKey] as string })),
)
fn: async function redo(this: UndoRedoAction, removedRowsData: Record<string, any>[]) {
const removedRowIds = await bulkDeleteRows(removedRowsData.map((row) => row.pkData))
if (Array.isArray(removedRowIds)) {
for (const { row } of removedRowsData) {
@ -846,7 +820,7 @@ export function useData(args: {
await callbacks?.syncPagination?.()
},
args: [removedRowsData, compositePrimaryKey],
args: [removedRowsData],
},
undo: {
fn: async function undo(

4
packages/nc-gui/composables/useExpandedFormStore.ts

@ -56,6 +56,8 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
: ({ row: {}, oldRow: {}, rowMeta: {} } as Row),
)
row.value.rowMeta.fromExpandedForm = true
const rowStore = useProvideSmartsheetRowStore(row)
const activeView = inject(ActiveViewInj, ref())
@ -304,6 +306,8 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
}
const loadRow = async (rowId?: string, onlyVirtual = false) => {
if (row.value.rowMeta.new) return
if (isPublic.value || !meta.value?.id) return
let record = await $api.dbTableRow.read(
NOCO,

6
packages/nc-gui/composables/useLTARStore.ts

@ -269,7 +269,11 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
// Mark out exact same objects in activeState[column.value.title] as Linked
// compare all keys and values
childrenExcludedList.value.list.forEach((row: any, index: number) => {
const found = activeState[column.value.title].find((a: any) => {
const found = (
[RelationTypes.BELONGS_TO, RelationTypes.ONE_TO_ONE].includes(colOptions.value.type)
? [activeState[column.value.title]]
: activeState[column.value.title]
).find((a: any) => {
let isSame = true
for (const key in a) {

1
packages/nc-gui/lib/types.ts

@ -65,6 +65,7 @@ interface Row {
changed?: boolean
saving?: boolean
ltarState?: Record<string, Record<string, any> | Record<string, any>[] | null>
fromExpandedForm?: boolean
// use in datetime picker component
isUpdatedFromCopyNPaste?: Record<string, boolean>
// Used in Calendar view

10
packages/nc-gui/utils/dataUtils.ts

@ -5,10 +5,12 @@ import type { Row } from '~/lib'
export const extractPkFromRow = (row: Record<string, any>, columns: ColumnType[]) => {
if (!row || !columns) return null
return columns
.filter((c) => c.pk)
.map((c) => row?.[c.title as string])
.join('___')
const pkColumns = columns.filter((c) => c.pk)
if (pkColumns.every((c) => row?.[c.title as string] === null && row?.[c.title as string] === undefined)) return null
return pkColumns.map((c) => row?.[c.title as string]).join('___')
}
export const rowPkData = (row: Record<string, any>, columns: ColumnType[]) => {

1
packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts

@ -643,6 +643,7 @@ export class SnowflakeUi {
case 'STRING':
return 'string';
case 'TEXT':
if (col.dtxp < 1024) return 'string';
return 'text';
case 'BINARY':
case 'VARBINARY':

144
packages/nocodb/src/db/BaseModelSqlv2.ts

@ -1897,7 +1897,7 @@ class BaseModelSqlv2 {
// .where(childTable.primaryKey.cn, cid)
.where(_wherePk(childTable.primaryKeys, cid))
.whereNotNull(cn),
).orWhereNull(rcn);
);
});
if (+rest?.shuffle) {
@ -2107,7 +2107,7 @@ class BaseModelSqlv2 {
] = async function (args): Promise<any> {
(listLoader as any).args = args;
return listLoader.load(
getCompositePk(self.model.primaryKeys, this),
getCompositePkValue(self.model.primaryKeys, this),
);
};
} else if (colOptions.type === 'mm') {
@ -2148,7 +2148,7 @@ class BaseModelSqlv2 {
] = async function (args): Promise<any> {
(listLoader as any).args = args;
return await listLoader.load(
getCompositePk(self.model.primaryKeys, this),
getCompositePkValue(self.model.primaryKeys, this),
);
};
} else if (colOptions.type === 'bt') {
@ -2370,7 +2370,7 @@ class BaseModelSqlv2 {
] = async function (args): Promise<any> {
(listLoader as any).args = args;
return listLoader.load(
getCompositePk(self.model.primaryKeys, this),
getCompositePkValue(self.model.primaryKeys, this),
);
};
}
@ -3124,7 +3124,7 @@ class BaseModelSqlv2 {
await this.prepareNocoData(insertObj, true, cookie);
await Promise.all(preInsertOps.map((f) => f(this.dbDriver)));
await this.runOps(preInsertOps.map((f) => f()));
let response;
const query = this.dbDriver(this.tnPath).insert(insertObj);
@ -3206,7 +3206,7 @@ class BaseModelSqlv2 {
}
rowId = this.extractCompositePK({ ai, ag, rowId, insertObj });
await Promise.all(postInsertOps.map((f) => f(rowId)));
await this.runOps(postInsertOps.map((f) => f(rowId)));
if (rowId !== null && rowId !== undefined) {
response = await this.readRecord({
@ -3254,7 +3254,20 @@ class BaseModelSqlv2 {
}
}
rowId = pkObj;
} else if (
!ai &&
!ag &&
(force || this.model.primaryKeys?.length > 1 || this.isSnowflake)
) {
// handle if primary key is not ai or ag
const pkObj = {};
for (const pk of this.model.primaryKeys) {
const key = pk.title;
pkObj[key] = insertObj[pk.column_name] ?? null;
}
rowId = pkObj;
}
return rowId;
}
@ -3267,8 +3280,8 @@ class BaseModelSqlv2 {
data: Record<string, any>;
insertObj: Record<string, any>;
}) {
const postInsertOps: ((rowId: any, trx?: any) => Promise<void>)[] = [];
const preInsertOps: ((trx?: any) => Promise<void>)[] = [];
const postInsertOps: ((rowId: any) => Promise<string>)[] = [];
const preInsertOps: (() => Promise<string>)[] = [];
for (const col of nestedCols) {
if (col.title in data) {
const colOptions = await col.getColOptions<LinkToAnotherRecordColumn>();
@ -3302,15 +3315,16 @@ class BaseModelSqlv2 {
if (isBt) {
// todo: unlink the ref record
preInsertOps.push(async (trx: any = this.dbDriver) => {
await trx(this.getTnPath(childModel.table_name))
preInsertOps.push(async () => {
return this.dbDriver(this.getTnPath(childModel.table_name))
.update({
[childCol.column_name]: null,
})
.where(
childCol.column_name,
nestedData[childModel.primaryKey.title],
);
)
.toQuery();
});
if (typeof nestedData !== 'object') continue;
@ -3318,15 +3332,16 @@ class BaseModelSqlv2 {
const parentCol = await colOptions.getParentColumn();
insertObj[childCol.column_name] = nestedData?.[parentCol.title];
} else {
postInsertOps.push(async (rowId, trx: any = this.dbDriver) => {
await trx(this.getTnPath(childModel.table_name))
postInsertOps.push(async (rowId) => {
return this.dbDriver(this.getTnPath(childModel.table_name))
.update({
[childCol.column_name]: rowId,
})
.where(
childModel.primaryKey.column_name,
nestedData[childModel.primaryKey.title],
);
)
.toQuery();
});
}
}
@ -3338,47 +3353,38 @@ class BaseModelSqlv2 {
const childModel = await childCol.getModel();
await childModel.getColumns();
postInsertOps.push(
async (
rowId,
// todo: use transaction type
trx: any = this.dbDriver,
) => {
await trx(this.getTnPath(childModel.table_name))
.update({
[childCol.column_name]: rowId,
})
.whereIn(
childModel.primaryKey.column_name,
nestedData?.map((r) => r[childModel.primaryKey.title]),
);
},
);
postInsertOps.push(async (rowId) => {
return this.dbDriver(this.getTnPath(childModel.table_name))
.update({
[childCol.column_name]: rowId,
})
.whereIn(
childModel.primaryKey.column_name,
nestedData?.map((r) => r[childModel.primaryKey.title]),
)
.toQuery();
});
}
break;
case RelationTypes.MANY_TO_MANY: {
if (!Array.isArray(nestedData)) continue;
postInsertOps.push(
async (
rowId,
// todo: use transaction type
trx: any = this.dbDriver,
) => {
const parentModel = await colOptions
.getParentColumn()
.then((c) => c.getModel());
await parentModel.getColumns();
const parentMMCol = await colOptions.getMMParentColumn();
const childMMCol = await colOptions.getMMChildColumn();
const mmModel = await colOptions.getMMModel();
const rows = nestedData.map((r) => ({
[parentMMCol.column_name]: r[parentModel.primaryKey.title],
[childMMCol.column_name]: rowId,
}));
await trx(this.getTnPath(mmModel.table_name)).insert(rows);
},
);
postInsertOps.push(async (rowId) => {
const parentModel = await colOptions
.getParentColumn()
.then((c) => c.getModel());
await parentModel.getColumns();
const parentMMCol = await colOptions.getMMParentColumn();
const childMMCol = await colOptions.getMMChildColumn();
const mmModel = await colOptions.getMMModel();
const rows = nestedData.map((r) => ({
[parentMMCol.column_name]: r[parentModel.primaryKey.title],
[childMMCol.column_name]: rowId,
}));
return this.dbDriver(this.getTnPath(mmModel.table_name))
.insert(rows)
.toQuery();
});
}
}
}
@ -3410,8 +3416,8 @@ class BaseModelSqlv2 {
try {
// TODO: ag column handling for raw bulk insert
const insertDatas = raw ? datas : [];
let postInsertOps: ((rowId: any, trx?: any) => Promise<void>)[] = [];
let preInsertOps: ((trx?: any) => Promise<void>)[] = [];
let postInsertOps: ((rowId: any) => Promise<string>)[] = [];
let preInsertOps: (() => Promise<string>)[] = [];
let aiPkCol: Column;
let agPkCol: Column;
@ -3596,7 +3602,10 @@ class BaseModelSqlv2 {
}
}
await Promise.all(preInsertOps.map((f) => f(trx)));
await this.runOps(
preInsertOps.map((f) => f()),
trx,
);
let responses;
@ -3661,7 +3670,10 @@ class BaseModelSqlv2 {
});
}
await Promise.all(postInsertOps.map((f) => f(rowId, trx)));
await this.runOps(
postInsertOps.map((f) => f(rowId)),
trx,
);
}
await trx.commit();
@ -3728,7 +3740,10 @@ class BaseModelSqlv2 {
const pkAndData: { pk: any; data: any }[] = [];
const readChunkSize = 100;
for (const [i, d] of updateDatas.entries()) {
const pkValues = this._extractPksValues(d);
const pkValues = getCompositePkValue(
this.model.primaryKeys,
this._extractPksValues(d),
);
if (!pkValues) {
// throw or skip if no pk provided
if (throwExceptionIfNotExist) {
@ -3954,7 +3969,10 @@ class BaseModelSqlv2 {
const pkAndData: { pk: any; data: any }[] = [];
const readChunkSize = 100;
for (const [i, d] of deleteIds.entries()) {
const pkValues = this._extractPksValues(d);
const pkValues = getCompositePkValue(
this.model.primaryKeys,
this._extractPksValues(d),
);
if (!pkValues) {
// throw or skip if no pk provided
if (throwExceptionIfNotExist) {
@ -5248,6 +5266,13 @@ class BaseModelSqlv2 {
return data;
}
async runOps(ops: Promise<string>[], trx = this.dbDriver) {
const queries = await Promise.all(ops);
for (const query of queries) {
await trx.raw(query);
}
}
protected async substituteColumnIdsWithColumnTitles(
data: Record<string, any>[],
dependencyColumns?: Column[],
@ -6863,8 +6888,9 @@ export function _wherePk(primaryKeys: Column[], id: unknown | unknown[]) {
return where;
}
function getCompositePk(primaryKeys: Column[], row) {
return primaryKeys.map((c) => row[c.title]).join('___');
export function getCompositePkValue(primaryKeys: Column[], row) {
if (typeof row !== 'object') return row;
return primaryKeys.map((c) => row[c.title] ?? row[c.column_name]).join('___');
}
export function haveFormulaColumn(columns: Column[]) {

1
packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaSnowflake.ts

@ -500,6 +500,7 @@ class ModelXcMetaSnowflake extends BaseModelXcMeta {
case 'smgr':
return dt;
case 'text':
if (col.dtxp < 1024) return 'string';
return 'text';
case 'tid':
return dt;

7
packages/nocodb/src/helpers/getUniqueName.ts

@ -1,6 +1,9 @@
import type Column from '~/models/Column';
export function getUniqueColumnName(columns: Column[], initialName = 'field') {
export function getUniqueColumnName(
columns: Partial<Column>[],
initialName = 'field',
) {
let c = 0;
while (
@ -13,7 +16,7 @@ export function getUniqueColumnName(columns: Column[], initialName = 'field') {
}
export function getUniqueColumnAliasName(
columns: Column[],
columns: Partial<Column>[],
initialName = 'field',
) {
let c = 0;

15
packages/nocodb/src/helpers/populateMeta.ts

@ -2,7 +2,7 @@ import { ModelTypes, UITypes, ViewTypes } from 'nocodb-sdk';
import { isVirtualCol, RelationTypes } from 'nocodb-sdk';
import { pluralize, singularize } from 'inflection';
import { isLinksOrLTAR } from 'nocodb-sdk';
import { getUniqueColumnAliasName } from './getUniqueName';
import { getUniqueColumnAliasName, getUniqueColumnName } from './getUniqueName';
import type { RollupColumn } from '~/models';
import type LinkToAnotherRecordColumn from '~/models/LinkToAnotherRecordColumn';
import type Source from '~/models/Source';
@ -93,7 +93,11 @@ export async function extractAndGenerateManyToManyRelations(
if (
belongsToCols?.length === 2 &&
normalColumns.length < 5 &&
assocModel.primaryKeys.length === 2
assocModel.primaryKeys.length === 2 &&
// check if both belongsToCol target primary keys
assocModel.primaryKeys.every((pk) =>
belongsToCols.some((c) => c.colOptions?.fk_child_column_id === pk.id),
)
) {
const modelA = await belongsToCols[0].colOptions.getRelatedTable();
const modelB = await belongsToCols[1].colOptions.getRelatedTable();
@ -383,8 +387,11 @@ export async function populateMeta(
base_id: base.id,
db_alias: source.id,
fk_model_id: models2[table.tn].id,
cn: column.cn,
title: column.title,
cn: getUniqueColumnName(models2[table.tn].columns, column.cn),
title: getUniqueColumnAliasName(
models2[table.tn].columns,
column.title,
),
uidt: column.uidt,
type: column.hm ? 'hm' : column.mm ? 'mm' : 'bt',
// column_id,

Loading…
Cancel
Save