diff --git a/media-store/.vscode/launch.json b/media-store/.vscode/launch.json index 51b33553..276cc90c 100644 --- a/media-store/.vscode/launch.json +++ b/media-store/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Launch Program", + "name": "Launch import", "command": "npm run import:data", "request": "launch", "skipFiles": ["/**"], diff --git a/media-store/db/schema.cds b/media-store/db/schema.cds index 8dc11839..6a88006a 100644 --- a/media-store/db/schema.cds +++ b/media-store/db/schema.cds @@ -1,17 +1,17 @@ namespace sap.capire.media.store; -aspect NamedEntityAspect { +aspect Named { key ID : Integer; name : String(120); } -aspect ContactInfoAspect { +aspect Persone { key ID : Integer; lastName : String(20); firstName : String(40); city : String(40); state : String(40); - adress : String(70); + address : String(70); country : String(40); postalCode : String(10); phone : String(24); @@ -19,16 +19,16 @@ aspect ContactInfoAspect { email : String(60); } -entity MediaTypes : NamedEntityAspect {} -entity Genres : NamedEntityAspect {} -entity Playlists : NamedEntityAspect {} +entity MediaTypes : Named {} +entity Genres : Named {} +entity Playlists : Named {} entity PlaylistTrack { key playlist : Association to Playlists; key track : Association to Tracks; } -entity Artists : NamedEntityAspect {} +entity Artists : Named {} entity Albums { key ID : Integer; @@ -36,61 +36,26 @@ entity Albums { artist : Association to Artists; } -entity Employees : ContactInfoAspect { +entity Employees : Persone { reportsTo : Association to Employees; title : String(20); birthDate : DateTime; hireDate : DateTime; } -entity Customers : ContactInfoAspect { +entity Customers : Persone { company : String(80); supportRep : Association to Employees; } -// keep columns order for importing data -// entity Employees { -// key ID : Integer; -// lastName : String(20); -// firstName : String(40); -// title : String(20); -// reportsTo : Association to Employees; -// birthDate : DateTime; -// hireDate : DateTime; -// adress : String(70); -// city : String(40); -// state : String(40); -// country : String(40); -// postalCode : String(10); -// phone : String(24); -// fax : String(24); -// email : String(60); -// } - -// entity Customers { -// key ID : Integer; -// firstName : String(40); -// lastName : String(20); -// company : String(80); -// adress : String(70); -// city : String(40); -// state : String(40); -// country : String(40); -// postalCode : String(10); -// phone : String(24); -// fax : String(24); -// email : String(60); -// supportRep : Association to Employees; -// } - entity Invoices { key ID : Integer; customer : Association to Customers; invoiceDate : DateTime; - billingAdress : String(70); + billingAddress : String(70); billingCity : String(40); billingState : String(40); - bilingCountry : String(40); + billingCountry : String(40); billingPostalCode : String(40); total : Decimal(10, 2); } diff --git a/media-store/package.json b/media-store/package.json index ff454e81..f876c74b 100644 --- a/media-store/package.json +++ b/media-store/package.json @@ -14,6 +14,7 @@ }, "scripts": { "start": "npx cds run", + "deploy:mychinookdb": "cds deploy --to sqlite:mychinook.db", "import:data": "node ./util/importData.js sqlite:chinook.db sqlite:mychinook.db ./db/schema.cds" }, "cds": { diff --git a/media-store/test/media-store.test.js b/media-store/test/media-store.test.js new file mode 100644 index 00000000..e69de29b diff --git a/media-store/util/importData.js b/media-store/util/importData.js index 08707497..a17f9163 100644 --- a/media-store/util/importData.js +++ b/media-store/util/importData.js @@ -1,11 +1,16 @@ const cds = require("@sap/cds"); const FIRST_INDEX = 0; +const ZERO_VALUE = 0; const SECOND_INDEX = 1; const SKIP_CLI_ARGS_NUMBER = 2; -const THIRD_INDEX = 3; +const THIRD_INDEX = 2; +const ID_IF_NOT_FOUND = "ID"; -const toValues = (object) => Object.values(object); +const args = process.argv.slice(SKIP_CLI_ARGS_NUMBER); +const SRC_STORAGE_NAME = args[FIRST_INDEX]; +const TARGET_STORAGE_NAME = args[SECOND_INDEX]; +const TARGET_SCHEMA_PATH = args[THIRD_INDEX]; const camelCaseToSnake = (str) => str.replace( @@ -16,52 +21,91 @@ const camelCaseToSnake = (str) => ].toLowerCase()}` ); +const omitID = (str) => { + return str.replace(/_ID/g, ""); +}; + +const omitId = (str) => { + return str.replace(/Id/g, ""); +}; + +const chooseFromTargetColumns = (targetColumns) => { + return (srcColumn) => { + const result = targetColumns.find((targetColumn) => { + const one = omitId(srcColumn).toUpperCase(); + const two = omitID(targetColumn).toUpperCase(); + if (one === two) { + return true; + } + return false; + }); + return result ? result : ID_IF_NOT_FOUND; + }; +}; + +const reorderTargetColumns = (srcColumns, targetColumns) => { + const mapColumn = chooseFromTargetColumns(targetColumns); + return srcColumns.map(mapColumn); +}; + const elementsToColumns = (elements) => { return Object.values(elements).map((element) => element.target ? `${element.name}_ID` : element.name ); }; -const constructInsertQuery = (targetEntityName, columns) => { - return (data) => INSERT.into(targetEntityName).columns(columns).rows(data); +const constructInsertQuery = (targetEntityName) => { + return (row, columns) => + INSERT.into(targetEntityName).columns(columns).rows(row); }; -(async () => { - const args = process.argv.slice(SKIP_CLI_ARGS_NUMBER); - const SRC_STORAGE_NAME = args[FIRST_INDEX]; - const TARGET_STORAGE_NAME = args[SECOND_INDEX]; - const TARGET_SCHEMA_PATH = args[THIRD_INDEX]; +const logProcessArgs = () => { console.log( - "[LOG]: Import data from", - SRC_STORAGE_NAME, - "to", - TARGET_STORAGE_NAME + `[LOG]: Import data from ${SRC_STORAGE_NAME} to ${TARGET_STORAGE_NAME}, schema path: ${TARGET_SCHEMA_PATH}` ); +}; +/** + * The MAIN ISSUE such import is that it depends on: + * - snake case table names + * - camel case column names + * of chinook.db + * There is 'Launch import' task in .vscode folder for debugging. + */ +(async () => { + logProcessArgs(); try { const srcStorage = await cds.connect.to(SRC_STORAGE_NAME); const targetStorage = await cds.connect.to(TARGET_STORAGE_NAME); - - const eList = targetStorage.entities; - const targetCSNModel = await cds.load(TARGET_SCHEMA_PATH); + const reflectedCSNModel = cds.reflect(targetCSNModel); const targetCSNEntities = Object.values(reflectedCSNModel.entities); for (index in targetCSNEntities) { const { name: targetEntityName, elements } = targetCSNEntities[index]; - const targetColumns = elementsToColumns(elements); - const insertQuery = constructInsertQuery(targetEntityName, targetColumns); + console.log(`[LOG]: Processing ${targetEntityName}`); + const targetColumns = elementsToColumns(elements); // e.g. ['ID', ..., 'total', 'customer_ID'] + const insertQuery = constructInsertQuery(targetEntityName, targetColumns); // e.g. { INSERT: ... } - const srcEntityName = camelCaseToSnake(targetEntityName.split(".").pop()); - const srcResultRows = (await srcStorage.read(srcEntityName)).map( - toValues - ); + const srcEntityName = camelCaseToSnake(targetEntityName.split(".").pop()); // e.g. playlist_track + const srcResultRows = await srcStorage.read(srcEntityName); // e.g. [ { AlbumId:1, ArtistId:1, Title:'some' }, ... ] - console.log("[LOG]: From", srcEntityName, "to", targetEntityName); + if (!srcResultRows || srcResultRows.length < ZERO_VALUE) { + continue; + } + const srcColumns = Object.keys(srcResultRows[FIRST_INDEX]); + const columns = reorderTargetColumns(srcColumns, targetColumns); + if (new Set(columns).size !== columns.length) { + throw new Error( + `Some ${targetEntityName} column name is mismatched in ${SRC_STORAGE_NAME} ${srcEntityName}` + ); + } const transaction = await targetStorage.tx(); - await transaction.run(srcResultRows.map(insertQuery)); + await transaction.run( + srcResultRows.map((row) => insertQuery(Object.values(row), columns)) + ); await transaction.commit(); } } catch (errors) {