diff --git a/forward_engineering/api.js b/forward_engineering/api.js index 5f866aa..894655c 100644 --- a/forward_engineering/api.js +++ b/forward_engineering/api.js @@ -21,13 +21,13 @@ const { GENERATING_CONTAINER_SCRIPT, GENERATING_ENTITY_SCRIPT, CREATING_A_BUCKET, -} = require('../shared/enums/static-messages'); +} = require('../shared/enums/staticMessages'); const { getCheckBucketExistsMessage, getCreatingBucketMessage, getSuccessfullyCreatedBucketMessage, -} = require('../shared/enums/dynamic-messages'); -const { HTTP_ERROR_CODES } = require('../shared/enums/http'); +} = require('../shared/enums/dynamicMessages'); +const { HTTP_ERROR_CODES } = require('../shared/enums/httpCodes'); const { applyScript, logApplyScriptAttempt } = require('./services/applyToInstanceService'); const ForwardEngineeringScriptBuilder = require('./services/forwardEngineeringScriptBuilder'); diff --git a/forward_engineering/services/applyToInstanceService.js b/forward_engineering/services/applyToInstanceService.js index b37ecd9..3065d5a 100644 --- a/forward_engineering/services/applyToInstanceService.js +++ b/forward_engineering/services/applyToInstanceService.js @@ -9,13 +9,13 @@ const { SCRIPT_SUCCESSFULLY_APPLIED, SUCCESSFULLY_APPLIED, ERROR_HAS_BEEN_THROWN_WHILE_APPLYING_SCRIPT_TO_COUCHBASE_INSTANCE, -} = require('../../shared/enums/static-messages'); +} = require('../../shared/enums/staticMessages'); const { getApplyingScriptPercentMessage, getRetryAttemptNumberMessage, getApplyingScriptToBucketWithAttemptNumberMessage, getApplyingScriptMessage, -} = require('../../shared/enums/dynamic-messages'); +} = require('../../shared/enums/dynamicMessages'); const { COUCHBASE_ERROR_CODE } = require('../../shared/constants'); const MAX_APPLY_ATTEMPTS = 5; diff --git a/forward_engineering/services/statements/commonStatements.js b/forward_engineering/services/statements/commonStatements.js index 107b345..2db192a 100644 --- a/forward_engineering/services/statements/commonStatements.js +++ b/forward_engineering/services/statements/commonStatements.js @@ -10,7 +10,7 @@ const wrapWithBackticks = str => `\`${str}\``; * @param {{statements: string[], separator: string}} param * @returns {string} */ -const joinStatements = ({ statements, separator = '\n\t' }) => `${statements.filter(Boolean).join(separator)}`; +const joinStatements = ({ statements = [], separator = '\n\t' }) => `${statements.filter(Boolean).join(separator)}`; /** * diff --git a/forward_engineering/services/statements/indexesStatements.js b/forward_engineering/services/statements/indexesStatements.js index 0c7bdb5..409c1bf 100644 --- a/forward_engineering/services/statements/indexesStatements.js +++ b/forward_engineering/services/statements/indexesStatements.js @@ -1,7 +1,7 @@ const { filter, get, isEmpty } = require('lodash'); const { getIndexKeyIdToKeyNameMap, injectKeysNamesIntoIndexKeys } = require('../../utils/indexes'); const { wrapWithBackticks, getKeySpaceReference, joinStatements } = require('./commonStatements'); -const { INDEX_TYPE } = require('../../../shared/enums/n1ql'); +const { INDEX_TYPE } = require('../../../shared/enums/indexType'); /** * @@ -85,7 +85,7 @@ const getKeys = index => { switch (index.indxType) { case INDEX_TYPE.primary: return { script: '', canHaveIndex: true }; - case INDEX_TYPE.secondary: + case INDEX_TYPE.secondary: { const keys = index.indxKey?.map(key => ({ ...key, name: wrapWithBackticks(key.name) })); const keysNames = joinStatements({ @@ -96,6 +96,7 @@ const getKeys = index => { }); return { script: `(${keysNames})`, canHaveIndex: Boolean(keysNames.length) }; + } case INDEX_TYPE.array: return { script: `(${index.arrayExpr})`, canHaveIndex: true }; case INDEX_TYPE.metadata: @@ -148,13 +149,19 @@ const getWhereClause = index => { */ const getWithClause = index => { const deferBuild = get(index, 'withOptions.defer_build') ? `"defer_build":true` : ''; + const numReplica = !isEmpty(get(index, 'withOptions.num_replica')) ? `"num_replica":${index.withOptions.num_replica}` : ''; - const nodes = get(index, 'withOptions.nodes', []).length - ? `"nodes":[${joinStatements({ statements: index.withOptions.nodes.map(node => `"${node.nodeName}"`), separator: ',' })}]` - : ''; + + const nodeStatement = joinStatements({ + statements: index.withOptions?.nodes?.map(node => `"${node.nodeName}"`), + separator: ',', + }); + const nodes = get(index, 'withOptions.nodes', []).length ? `"nodes":[${nodeStatement}]` : ''; + const hasWithClosure = deferBuild || numReplica || nodes; + const withClosure = joinStatements({ statements: [deferBuild, numReplica, nodes], separator: ',' }); return hasWithClosure ? `WITH{${withClosure}}` : ''; @@ -190,13 +197,14 @@ const getOrder = order => { */ const getPartitionByHashClause = index => { switch (index.partitionByHash) { - case 'Keys': + case 'Keys': { const keysNames = joinStatements({ statements: index.partitionByHashKeys.map(key => wrapWithBackticks(key.name)), separator: ',', }); return `PARTITION BY HASH(${keysNames})`; + } case 'Expression': return `PARTITION BY HASH(${index.partitionByHashExpr})`; default: @@ -209,8 +217,13 @@ const getPartitionByHashClause = index => { * @param {string} statement * @returns {string} */ -const commentStatement = statement => - `/*\n${joinStatements({ statements: statement.split('\n').map(line => ` * ${line}`), separator: '\n' })}\n */`; +const commentStatement = statement => { + const joinedStatement = joinStatements({ + statements: statement.split('\n').map(line => ` * ${line}`), + separator: '\n', + }); + return `/*\n${joinedStatement}\n */`; +}; module.exports = { getIndexesScript, diff --git a/package-lock.json b/package-lock.json index 2457cf2..658a340 100644 --- a/package-lock.json +++ b/package-lock.json @@ -495,6 +495,18 @@ "@smithy/types": "3.6.0" } }, + "node_modules/@hackolade/fetch/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@hackolade/hck-esbuild-plugins-pack": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/@hackolade/hck-esbuild-plugins-pack/-/hck-esbuild-plugins-pack-0.0.1.tgz", @@ -635,11 +647,22 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", - "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", + "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", + "dependencies": { + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/protocol-http/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", "dependencies": { - "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { diff --git a/reverse_engineering/api.js b/reverse_engineering/api.js index 725020c..8c194ed 100644 --- a/reverse_engineering/api.js +++ b/reverse_engineering/api.js @@ -144,7 +144,7 @@ const getDbCollectionsData = async (data, appLogger, callback, app) => { }); try { - const connectionInfo = data.connectionInfo; + const connectionInfo = data; const includeEmptyCollection = data.includeEmptyCollection; const bucketName = data.database; const collections = data.collectionData.collections; diff --git a/reverse_engineering/helpers/indexHelper.js b/reverse_engineering/helpers/indexHelper.js index 2305f2e..1dcae7c 100644 --- a/reverse_engineering/helpers/indexHelper.js +++ b/reverse_engineering/helpers/indexHelper.js @@ -3,6 +3,7 @@ const restApiHelper = require('./restApiHelper'); const clusterHelper = require('../../shared/helpers/clusterHelper'); const parserHelper = require('./parserHelper'); const { GET_META_REGEXP, GET_PARTITION_HASH_REGEXP, DEFAULT_NAME } = require('../../shared/constants'); +const { INDEX_TYPE } = require('../../shared/enums/indexType'); const handleIndex = index => { const indexData = getHackoladeCompatibleIndex(index); @@ -13,13 +14,13 @@ const getHackoladeCompatibleIndex = index => { if (index.is_primary) { return { indxName: index.name, - indxType: 'Primary', + indxType: INDEX_TYPE.primary, usingGSI: index.using === 'gsi', }; } else if (checkArrayIndex(index)) { return { indxName: index.name, - indxType: 'Array', + indxType: INDEX_TYPE.array, usingGSI: index.using === 'gsi', arrayExpr: index.index_key.map(getExpression).join(','), whereClause: getWhereCondition(index), @@ -27,7 +28,7 @@ const getHackoladeCompatibleIndex = index => { } else if (checkMetaIndex(index)) { return { indxName: index.name, - indxType: 'Metadata', + indxType: INDEX_TYPE.metadata, metadataExpr: index.index_key.map(getExpression).join(','), }; } else { @@ -36,7 +37,7 @@ const getHackoladeCompatibleIndex = index => { return { indxName: index.name, - indxType: 'Secondary', + indxType: INDEX_TYPE.secondary, usingGSI: index.using === 'gsi', indxKey: keys, functionExpr: expression, diff --git a/reverse_engineering/helpers/queryHelper.js b/reverse_engineering/helpers/queryHelper.js index 65d62ca..083d1ec 100644 --- a/reverse_engineering/helpers/queryHelper.js +++ b/reverse_engineering/helpers/queryHelper.js @@ -1,4 +1,6 @@ +const { isEmpty } = require('lodash'); const { NUM_SAMPLE_VALUES } = require('../../shared/constants'); +const { INDEX_TYPE } = require('../../shared/enums/indexType'); /** * @param {{ bucketName: string; scopeName: string; collectionName: string; limit: number }} param0 @@ -18,11 +20,41 @@ const getSelectBucketDocumentsQuery = ({ bucketName, limit, offset }) => { }; /** - * @param {{ bucketName: string; scopeName: string; collectionName: string; limit: number; offset: number }} param0 + * @param {{ collectionIndexes: object[] }} param0 * @returns {string} */ -const getSelectCollectionDocumentsQuery = ({ bucketName, scopeName, collectionName, limit, offset }) => { - const query = `SELECT *, META().id AS docid FROM \`${bucketName}\`.\`${scopeName}\`.\`${collectionName}\` AS \`${bucketName}\``; +const getWhereClauseFromIndexes = ({ collectionIndexes = [] }) => { + // primary index allows to `select` the document id without extra WHERE clause + const primaryIndex = collectionIndexes.find(index => index.indxType === INDEX_TYPE.primary); + + if (isEmpty(collectionIndexes) || primaryIndex) { + return ''; + } + + return `WHERE ${collectionIndexes[0].indxKey[0].name} LIKE '%'`; +}; + +/** + * @param {{ + * bucketName: string; + * scopeName: string; + * collectionName: string; + * collectionIndexes: object[]; + * limit: number; + * offset: number + * }} param0 + * @returns {string} + */ +const getSelectCollectionDocumentsQuery = ({ + bucketName, + scopeName, + collectionName, + collectionIndexes, + limit, + offset, +}) => { + const whereClause = getWhereClauseFromIndexes({ collectionIndexes }); + const query = `SELECT *, META().id AS docid FROM \`${bucketName}\`.\`${scopeName}\`.\`${collectionName}\` AS \`${bucketName}\` ${whereClause}`; return getQueryOptions({ query, limit, offset }); }; diff --git a/reverse_engineering/helpers/restApiHelper.js b/reverse_engineering/helpers/restApiHelper.js index 6a32688..a566e75 100644 --- a/reverse_engineering/helpers/restApiHelper.js +++ b/reverse_engineering/helpers/restApiHelper.js @@ -49,8 +49,8 @@ class CouchbaseRestApiService { Authorization: `Basic ${encodedCredentials}`, }, }; - - return hckFetch(uri, options); + const response = await hckFetch(uri, options); + return await response.json(); } catch (error) { throw new CustomError({ message: error.statusText || error.message, diff --git a/shared/constants.js b/shared/constants.js index 175a39d..5225b04 100644 --- a/shared/constants.js +++ b/shared/constants.js @@ -57,7 +57,7 @@ const COUCHBASE_DEFAULT_KV_CONNECTION_PORT = 11210; const DISABLED_TOOLTIP = 'Something went wrong. Please, check logs for more details'; const GET_META_REGEXP = /\(meta\(\)\.(.*?)\)/; -const GET_NODES_REGEXP = /"nodes":(\[.*?\])/; +const GET_NODES_REGEXP = /"nodes":(\[.*?])/; const GET_PARTITION_HASH_REGEXP = /(HASH|hash)\((.*?)\)$/; /** diff --git a/shared/enums/dynamic-messages.js b/shared/enums/dynamicMessages.js similarity index 100% rename from shared/enums/dynamic-messages.js rename to shared/enums/dynamicMessages.js diff --git a/shared/enums/http.js b/shared/enums/httpCodes.js similarity index 100% rename from shared/enums/http.js rename to shared/enums/httpCodes.js diff --git a/shared/enums/n1ql.js b/shared/enums/indexType.js similarity index 100% rename from shared/enums/n1ql.js rename to shared/enums/indexType.js diff --git a/shared/enums/static-messages.js b/shared/enums/staticMessages.js similarity index 100% rename from shared/enums/static-messages.js rename to shared/enums/staticMessages.js diff --git a/shared/helpers/clusterHelper.js b/shared/helpers/clusterHelper.js index 1811ecb..33f5a3b 100644 --- a/shared/helpers/clusterHelper.js +++ b/shared/helpers/clusterHelper.js @@ -285,7 +285,12 @@ const getDbCollectionData = async ({ logger, }); const options = { limit, pagination: data.pagination, bucketName, scopeName, collectionName }; - const query = queryHelper.getSelectCollectionDocumentsQuery({ bucketName, scopeName, collectionName }); + const query = queryHelper.getSelectCollectionDocumentsQuery({ + bucketName, + scopeName, + collectionName, + collectionIndexes, + }); const documents = await getPaginatedQuery({ cluster, options, query, logger }); const standardDocument = await getCollectionDocumentByDocumentId({ cluster,