[bug] Fix sql for oracle NCLOB type; Fix bug 77666

This commit is contained in:
Sergey Konovalov
2025-10-16 15:35:52 +03:00
parent 6332a524f7
commit 9a4680497e
4 changed files with 53 additions and 18 deletions

View File

@ -439,6 +439,17 @@ function getTableColumns(ctx, tableName) {
});
}
/**
* Generate SQL condition to check if a field is not empty (fallback implementation)
* Database-specific connectors can override this function
* @param {string} fieldName - Name of the field to check
* @returns {string} SQL condition string
*/
function getNotEmptyConditionFallback(fieldName) {
// Default implementation for most databases: check both NULL and empty string
return `${fieldName} IS NOT NULL AND ${fieldName} != ''`;
}
module.exports = {
insertChangesPromise,
deleteChangesPromise,
@ -454,5 +465,7 @@ module.exports = {
getTableColumns,
getDateTime: _getDateTime2,
...connectorUtilities,
...dbInstance
...dbInstance,
// Use connector-specific implementation if available, otherwise use fallback
getNotEmptyCondition: dbInstance.getNotEmptyCondition || getNotEmptyConditionFallback
};

View File

@ -445,6 +445,16 @@ async function insertChangesAsync(ctx, tableChanges, startIndex, objChanges, doc
return result;
}
/**
* Generate SQL condition to check if a field is not empty
* Oracle-specific: NCLOB cannot be compared with != operator, and empty strings are NULL
* @param {string} fieldName - Name of the field to check
* @returns {string} SQL condition string
*/
function getNotEmptyCondition(fieldName) {
return `${fieldName} IS NOT NULL`;
}
module.exports = {
sqlQuery,
closePool,
@ -456,5 +466,6 @@ module.exports = {
getDocumentsWithChanges,
getExpired,
upsert,
insertChanges
insertChanges,
getNotEmptyCondition
};

View File

@ -135,7 +135,7 @@ function select(ctx, docId) {
});
}
/**
* Convert task object to SQL update/condition array
* Generate SQL SET/WHERE clauses from task object
* @param {TaskResultData} task - Task data object
* @param {boolean} updateTime - Whether to update last_open_date
* @param {boolean} isMask - Whether this is for WHERE clause (mask mode)
@ -145,7 +145,7 @@ function select(ctx, docId) {
*
* Special mask values:
* - Use 'NOT_EMPTY' as field value in mask mode to check for non-empty callback
* - Example: {callback: 'NOT_EMPTY'} generates "callback IS NOT NULL AND callback != ''"
* - Uses baseConnector.getNotEmptyCondition() for database-specific SQL generation
*/
function toUpdateArray(task, updateTime, isMask, values, setPassword) {
const res = [];
@ -177,7 +177,8 @@ function toUpdateArray(task, updateTime, isMask, values, setPassword) {
}
// Add callback non-empty check for mask
if (isMask && task.callback === 'NOT_EMPTY') {
res.push(`callback IS NOT NULL AND callback != ''`);
// Use database-specific condition (Oracle NCLOB needs special handling)
res.push(sqlBase.getNotEmptyCondition('callback'));
}
if (null != task.baseurl) {
const sqlParam = addSqlParam(task.baseurl, values);

View File

@ -210,7 +210,14 @@ afterAll(async () => {
const updateIfIds = Object.values(updateIfCases);
const tableChangesIds = [...emptyCallbacksCase, ...documentsWithChangesCase, ...changesIds, ...insertIds];
const tableResultIds = [...emptyCallbacksCase, ...documentsWithChangesCase, ...getExpiredCase, ...getCountWithStatusCase, ...upsertIds, ...updateIfIds];
const tableResultIds = [
...emptyCallbacksCase,
...documentsWithChangesCase,
...getExpiredCase,
...getCountWithStatusCase,
...upsertIds,
...updateIfIds
];
const deletionPool = [
deleteRowsByIds(cfgTableChanges, tableChangesIds),
@ -292,17 +299,23 @@ describe('Base database connector', () => {
describe('Add changes', () => {
for (const testCase in insertCases) {
test(`${testCase} rows inserted`, async () => {
const docId = insertCases[testCase];
const objChanges = createChanges(+testCase, date);
// Increase timeout for large inserts (5000+ rows can take longer on some databases)
const timeout = +testCase >= 5000 ? 15000 : 5000;
test(
`${testCase} rows inserted`,
async () => {
const docId = insertCases[testCase];
const objChanges = createChanges(+testCase, date);
await noRowsExistenceCheck(cfgTableChanges, docId);
await noRowsExistenceCheck(cfgTableChanges, docId);
await baseConnector.insertChangesPromise(ctx, objChanges, docId, index, user);
const result = await getRowsCountById(cfgTableChanges, docId);
await baseConnector.insertChangesPromise(ctx, objChanges, docId, index, user);
const result = await getRowsCountById(cfgTableChanges, docId);
expect(result).toEqual(objChanges.length);
});
expect(result).toEqual(objChanges.length);
},
timeout
);
}
});
@ -482,10 +495,7 @@ describe('Base database connector', () => {
const taskEmptyCallback = createTask(updateIfCases.emptyCallback, '');
// Insert two rows: one with callback, one without
await Promise.all([
insertIntoResultTable(date, taskWithCallback),
insertIntoResultTable(date, taskEmptyCallback)
]);
await Promise.all([insertIntoResultTable(date, taskWithCallback), insertIntoResultTable(date, taskEmptyCallback)]);
// Update mask: only update rows with non-empty callback and status=None
const mask = new taskResult.TaskResultData();