From ac87bac6c6ff40793f72bb8546d85e7da693a755 Mon Sep 17 00:00:00 2001 From: Serhii Filonenko <91055067+serhii-filonenko@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:58:52 +0300 Subject: [PATCH] HCK-6309: american express support for couchbasev7plus fle (#34) * HCK-6309: add PP config for PK structure * HCK-6309: add randexp module * HCK-6309: add generating key sample by PK structure * HCK-6309: clear js docs * update package-lock * fix prettier issue --- .../getPrimaryKeySampleByStructure.js | 89 ++++++++++++++++++ .../services/statements/insertStatements.js | 14 +-- package-lock.json | 29 ++++++ package.json | 7 +- .../field_level/fieldLevelConfig.json | 93 +++++++++++++++++++ shared/constants.js | 11 +++ shared/types.d.ts | 13 ++- 7 files changed, 242 insertions(+), 14 deletions(-) create mode 100644 forward_engineering/services/statements/getPrimaryKeySampleByStructure.js diff --git a/forward_engineering/services/statements/getPrimaryKeySampleByStructure.js b/forward_engineering/services/statements/getPrimaryKeySampleByStructure.js new file mode 100644 index 0000000..82f2de4 --- /dev/null +++ b/forward_engineering/services/statements/getPrimaryKeySampleByStructure.js @@ -0,0 +1,89 @@ +/** + * @typedef {import('../../../shared/types').PkSegment} PkSegment + * @typedef {import('../../../shared/types').UUID} UUID + */ +const RandExp = require('randexp'); +const { PK_SEGMENT_TYPE } = require('../../../shared/constants'); + +/** + * @param {{ collection: object, jsonData: object }} + * @returns {string} + */ +const getPrimaryKeySampleByStructure = ({ collection, jsonData }) => { + const keyField = Object.values(collection.properties || {}).find(field => field.primaryKeyStructure); + const primaryKeyStructure = keyField?.primaryKeyStructure; + + if (!Array.isArray(primaryKeyStructure)) { + return ''; + } + + return primaryKeyStructure.reduce((result, segment) => { + switch (segment.segmentType) { + case PK_SEGMENT_TYPE.field: { + const segmentValue = getPrimaryKeyStructureFieldValue({ segment, collection, jsonData }); + return result + segmentValue; + } + case PK_SEGMENT_TYPE.pattern: { + const segmentValue = getPrimaryKeyStructurePatternValue({ segment }); + return result + segmentValue; + } + case PK_SEGMENT_TYPE.constant: + case PK_SEGMENT_TYPE.separator: { + return result + (segment.segmentValue ?? ''); + } + default: + return result; + } + }, ''); +}; + +/** + * @param {{ segment: PkSegment, collection: object, jsonData: object }} + * @returns {string} + */ +const getPrimaryKeyStructureFieldValue = ({ segment, collection, jsonData }) => { + const fieldNames = (segment.segmentKey || []).map(({ keyId }) => { + return findFieldNameById({ collection, id: keyId }); + }); + + return fieldNames + .filter(Boolean) + .map(fieldName => jsonData[fieldName]) + .join(''); +}; + +/** + * @param {{ segment: PkSegment }} + * @returns {string} + */ +const getPrimaryKeyStructurePatternValue = ({ segment }) => { + try { + const randExpInstance = new RandExp(segment.segmentRegex); + + return randExpInstance.gen(); + } catch (e) { + return segment.segmentSample ?? ''; + } +}; + +/** + * @param {{ collection: object, id: UUID }} + * @returns {string} + */ +const findFieldNameById = ({ collection, id }) => { + return Object.entries(collection.properties || {}).reduce((result, [fieldName, field]) => { + if (result) { + return result; + } + + if (field.GUID === id) { + return fieldName; + } + + return findFieldNameById({ collection: field, id }); + }, ''); +}; + +module.exports = { + getPrimaryKeySampleByStructure, +}; diff --git a/forward_engineering/services/statements/insertStatements.js b/forward_engineering/services/statements/insertStatements.js index 180f500..d27de31 100644 --- a/forward_engineering/services/statements/insertStatements.js +++ b/forward_engineering/services/statements/insertStatements.js @@ -1,5 +1,6 @@ const uuid = require('uuid'); const { getKeySpaceReference } = require('./commonStatements'); +const { getPrimaryKeySampleByStructure } = require('./getPrimaryKeySampleByStructure'); /** * @@ -38,21 +39,14 @@ const getInsertScriptForCollection = ({ jsonData, collection }) => { const isKeyGeneratedWithFakerFunction = collection?.properties?.[keyPropertyName]?.fakerFunction; const parseJsonData = JSON.parse(jsonData); const sampleValue = collection?.properties?.[keyPropertyName]?.sample; - const keyValue = isKeyGeneratedWithFakerFunction ? parseJsonData[keyPropertyName] : sampleValue; + const keySample = isKeyGeneratedWithFakerFunction ? parseJsonData[keyPropertyName] : sampleValue; const { [keyPropertyName]: keyProperty, ...jsonDataBody } = parseJsonData; - - const sampledKey = getKeyFieldSample(keyValue); + const pkSample = getPrimaryKeySampleByStructure({ collection, jsonData: parseJsonData }); + const sampledKey = pkSample || keySample || uuid.v4(); return `INSERT INTO ${insertionPath} (KEY, VALUE)\n\tVALUES("${sampledKey}",${JSON.stringify(jsonDataBody, null, '\t')});`; }; -/** - * - * @param {string} jsonDataKey - * @returns {string} - */ -const getKeyFieldSample = jsonDataKey => jsonDataKey || uuid.v4(); - module.exports = { getInsertScripts, getInsertScriptForCollection, diff --git a/package-lock.json b/package-lock.json index de613d3..e83140f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "async": "3.2.5", "exponential-backoff": "3.1.1", "lodash": "4.17.21", + "randexp": "0.5.3", "uuid": "9.0.1" }, "devDependencies": { @@ -1370,6 +1371,14 @@ "node": ">=6.0.0" } }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "engines": { + "node": ">=4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3472,6 +3481,18 @@ } ] }, + "node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -3556,6 +3577,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", diff --git a/package.json b/package.json index 0e2bf25..ea1f521 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,12 @@ "package": "node esbuild.package.js" }, "dependencies": { + "antlr4": "4.9.2", "async": "3.2.5", "exponential-backoff": "3.1.1", "lodash": "4.17.21", - "uuid": "9.0.1", - "antlr4": "4.9.2" + "randexp": "0.5.3", + "uuid": "9.0.1" }, "devDependencies": { "@hackolade/hck-esbuild-plugins-pack": "0.0.1", @@ -54,4 +55,4 @@ "prettier": "3.2.5", "simple-git-hooks": "2.11.1" } -} \ No newline at end of file +} diff --git a/properties_pane/field_level/fieldLevelConfig.json b/properties_pane/field_level/fieldLevelConfig.json index a2a8557..0a15f64 100644 --- a/properties_pane/field_level/fieldLevelConfig.json +++ b/properties_pane/field_level/fieldLevelConfig.json @@ -135,6 +135,99 @@ making sure that you maintain a proper JSON format. "required", "dependencies", "primaryKey", + { + "propertyName": "PK Structure", + "propertyKeyword": "primaryKeyStructure", + "propertyTooltip": "For user-defined key structure, describe the different segments for the PK", + "propertyType": "group", + "structure": [ + { + "propertyName": "Segment type", + "propertyKeyword": "segmentType", + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "parentType": "primaryKeyStructure", + "options": ["constant", "separator", "field", "pattern"] + }, + { + "propertyName": "Meaning", + "propertyKeyword": "segmentMeaning", + "propertyTooltip": "Keyword describing the segment", + "propertyType": "text", + "parentType": "primaryKeyStructure", + "dependency": { + "type": "or", + "values": [ + { + "key": "segmentType", + "value": "constant" + }, + { + "key": "segmentType", + "value": "pattern" + } + ] + } + }, + { + "propertyName": "Keys", + "propertyKeyword": "segmentKey", + "propertyType": "fieldList", + "template": "orderedList", + "parentType": "primaryKeyStructure", + "dependency": { + "key": "segmentType", + "value": "field" + } + }, + { + "propertyName": "Value", + "propertyKeyword": "segmentValue", + "propertyTooltip": "Value for constants and separators", + "propertyType": "text", + "parentType": "primaryKeyStructure", + "dependency": { + "type": "or", + "values": [ + { + "key": "segmentType", + "value": "constant" + }, + { + "key": "segmentType", + "value": "separator" + } + ] + } + }, + { + "propertyName": "Regex", + "propertyKeyword": "segmentRegex", + "propertyTooltip": "", + "propertyType": "text", + "parentType": "primaryKeyStructure", + "dependency": { + "key": "segmentType", + "value": "pattern" + } + }, + { + "propertyName": "Sample", + "propertyKeyword": "segmentSample", + "propertyTooltip": "", + "propertyType": "text", + "parentType": "primaryKeyStructure", + "dependency": { + "key": "segmentType", + "value": "pattern" + } + } + ], + "dependency": { + "key": "containerLevelKey", + "value": true + } + }, "foreignCollection", "foreignField", "relationshipType", diff --git a/shared/constants.js b/shared/constants.js index 0c41085..175a39d 100644 --- a/shared/constants.js +++ b/shared/constants.js @@ -60,6 +60,16 @@ const GET_META_REGEXP = /\(meta\(\)\.(.*?)\)/; const GET_NODES_REGEXP = /"nodes":(\[.*?\])/; const GET_PARTITION_HASH_REGEXP = /(HASH|hash)\((.*?)\)$/; +/** + * @enum {string} + */ +const PK_SEGMENT_TYPE = { + constant: 'constant', + field: 'field', + pattern: 'pattern', + separator: 'separator', +}; + module.exports = { AUTH_TYPE, COUCHBASE_ERROR_CODE, @@ -75,4 +85,5 @@ module.exports = { GET_PARTITION_HASH_REGEXP, HOSTING, STATUS, + PK_SEGMENT_TYPE, }; diff --git a/shared/types.d.ts b/shared/types.d.ts index e49c551..f944df0 100644 --- a/shared/types.d.ts +++ b/shared/types.d.ts @@ -1,5 +1,5 @@ import { Cluster, Scope, Bucket } from 'couchbase'; -import { STATUS } from './constants'; +import { PK_SEGMENT_TYPE } from './constants'; type UUID = string; @@ -112,6 +112,16 @@ type InferenceProperty = { type: string | Array } +type PkSegment = { + segmentType: Values; + segmentValue: string; + segmentPattern?: string; + segmentSample?: string; + segmentMeaning?: string; + segmentRegex?: string; + segmentKey?: Array<{ keyId: UUID }>; +}; + export { App, AppLogger, @@ -132,4 +142,5 @@ export { Scope, UUID, InferenceProperty, + PkSegment, };