diff --git a/__tests__/createIndexRaw.spec.ts b/__tests__/createIndexRaw.spec.ts new file mode 100644 index 0000000..5848cbd --- /dev/null +++ b/__tests__/createIndexRaw.spec.ts @@ -0,0 +1,29 @@ +import { createIndexRaw, createLocalIndexRaw } from '../lib'; + +it('Can create a basic table', async () => { + expect( + createIndexRaw<{ buildings: { name: string, city: string } }, 'buildings', 'name' | 'city'>( + 'scyllo', + 'buildings', + 'buildings_city', + 'city' + ) + ).toEqual({ + args: [], + query: 'CREATE INDEX IF NOT EXISTS buildings_city ON scyllo.buildings (city)', + }); +}); + +it('Can create a basic table if not exists', async () => { + expect( + createLocalIndexRaw<{ buildings: { name: string, city: string } }, 'buildings', 'name' | 'city'>( + 'scyllo', + 'buildings', + 'buildings_index', + 'name', + 'city' + )).toEqual({ + args: [], + query: 'CREATE INDEX IF NOT EXISTS buildings_index ON scyllo.buildings ((name), city)', + }); +}); diff --git a/package.json b/package.json index fbe3df6..879c770 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "typescript-eslint": "^0.0.1-alpha.0" }, "scripts": { - "test": "jest", + "test": "yarn build && jest", "build": "tsc", "lint": "eslint -c .eslintrc.json --ext .ts ./src", "pub": "yarn build && yarn publish --access public" diff --git a/src/QueryBuilder.ts b/src/QueryBuilder.ts index e6d6212..7f4f3bb 100644 --- a/src/QueryBuilder.ts +++ b/src/QueryBuilder.ts @@ -4,38 +4,167 @@ import { TableScheme } from './ScylloClient'; import { toScyllo } from './ScylloTranslator'; export type QueryBuild = { - query: string, - args: any[] + query: string; + args: any[]; }; -export const selectFromRaw = (keyspace: string, table: F, select: '*' | (keyof TableMap[F])[], criteria?: { [key in keyof TableMap[F]]?: TableMap[F][key] | string }, extra?: string): QueryBuild => ({ - query: `SELECT ${select == '*' ? select : select.join(',')} FROM ${keyspace}.${table} ${criteria && Object.keys(criteria).length > 0 ? ('WHERE ' + Object.keys(criteria).map(crit => crit + '=?').join(' AND ')) : ''} ${extra || ''}`.trim(), - args: [...(criteria ? Object.values(criteria) : [])] -}); - -export const selectOneFromRaw = (keyspace: string, table: F, select: '*' | (keyof TableMap[F])[], criteria?: { [key in keyof TableMap[F]]?: TableMap[F][key] | string }, extra?: string): QueryBuild => ({ - query: `SELECT ${select == '*' ? select : select.join(',')} FROM ${keyspace}.${table} ${criteria && Object.keys(criteria).length > 0 ? ('WHERE ' + Object.keys(criteria).map(crit => crit + '=?').join(' AND ')) : ''} LIMIT 1 ${extra || ''}`.trim(), - args: [...(criteria ? Object.values(criteria) : [])] -}); - -export const insertIntoRaw = (keyspace: string, table: F, obj: Partial): QueryBuild => ({ - query: `INSERT INTO ${keyspace}.${table} (${Object.keys(obj).join(', ')}) VALUES (${Object.keys(obj).map(() => '?').join(', ')})`, - args: Object.values(obj).map(toScyllo) -}); - -export const updateRaw = (keyspace: string, table: TableName, obj: Partial, criteria: { [key in ColumnName]?: TableMap[TableName][key] | string }): QueryBuild => ({ - query: `UPDATE ${keyspace}.${table} SET ${Object.keys(obj).map(it => it + '=?').join(',')} ${criteria && Object.keys(criteria).length > 0 ? 'WHERE ' + Object.keys(criteria).map(crit => crit += '=?').join(' AND ') : ''}`.trim(), - args: [...Object.values(obj), ...(criteria ? Object.values(criteria) : [])] -}); - -export const deleteFromRaw = (keyspace: string, table: TableName, fields: '*' | ColumnName[], criteria: { [key in ColumnName]?: TableMap[TableName][key] | string }, extra?: string): QueryBuild => ({ - query: `DELETE ${fields == '*' ? '' : fields.join(',')} ${keyspace}.${table} ${criteria && Object.keys(criteria).length > 0 ? ('WHERE ' + Object.keys(criteria).map(crit => crit + '=?').join(' AND ')) : ''} ${extra || ''}`.trim(), - args: [...(criteria ? Object.values(criteria) : [])] -}); - -export type ColumnType = { type: keyof typeof types.dataTypes, test?: string }; - -export const createTableRaw = (keyspace: string, table: F, createIfNotExists: boolean, columns: { [key in keyof TableMap[F]]: ColumnType }, partition: [keyof TableMap[F], keyof TableMap[F]] | keyof TableMap[F], clustering?: (keyof TableMap[F])[]): QueryBuild => ({ - query: `CREATE TABLE${createIfNotExists ? ' IF NOT EXISTS' : ''} ${keyspace}.${table} (${(Object.keys(columns) as (keyof TableMap[F])[]).map((a) => a + ' ' + columns[a].type).join(',')}, PRIMARY KEY (${partition instanceof Array ? '(' + partition.join(',') + ')' : partition}${clustering ? `, ${clustering.join(',')}` : ''}))`, - args: [] -}); \ No newline at end of file +export const selectFromRaw = < + TableMap extends TableScheme, + F extends keyof TableMap, +>( + keyspace: string, + table: F, + select: '*' | (keyof TableMap[F])[], + criteria?: { [key in keyof TableMap[F]]?: TableMap[F][key] | string }, + extra?: string, + ): QueryBuild => ({ + query: `SELECT ${ + select == '*' ? select : select.join(',') + } FROM ${keyspace}.${table} ${ + criteria && Object.keys(criteria).length > 0 + ? 'WHERE ' + + Object.keys(criteria) + .map((crit) => crit + '=?') + .join(' AND ') + : '' + } ${extra || ''}`.trim(), + args: [...(criteria ? Object.values(criteria) : [])], + }); + +export const selectOneFromRaw = < + TableMap extends TableScheme, + F extends keyof TableMap, +>( + keyspace: string, + table: F, + select: '*' | (keyof TableMap[F])[], + criteria?: { [key in keyof TableMap[F]]?: TableMap[F][key] | string }, + extra?: string, + ): QueryBuild => ({ + query: `SELECT ${ + select == '*' ? select : select.join(',') + } FROM ${keyspace}.${table} ${ + criteria && Object.keys(criteria).length > 0 + ? 'WHERE ' + + Object.keys(criteria) + .map((crit) => crit + '=?') + .join(' AND ') + : '' + } LIMIT 1 ${extra || ''}`.trim(), + args: [...(criteria ? Object.values(criteria) : [])], + }); + +export const insertIntoRaw = < + TableMap extends TableScheme, + F extends keyof TableMap, +>( + keyspace: string, + table: F, + obj: Partial, + ): QueryBuild => ({ + query: `INSERT INTO ${keyspace}.${table} (${Object.keys(obj).join( + ', ', + )}) VALUES (${Object.keys(obj) + .map(() => '?') + .join(', ')})`, + args: Object.values(obj).map(toScyllo), + }); + +export const updateRaw = < + TableMap extends TableScheme, + TableName extends keyof TableMap, + ColumnName extends keyof TableMap[TableName], +>( + keyspace: string, + table: TableName, + obj: Partial, + criteria: { [key in ColumnName]?: TableMap[TableName][key] | string }, + ): QueryBuild => ({ + query: `UPDATE ${keyspace}.${table} SET ${Object.keys(obj) + .map((it) => it + '=?') + .join(',')} ${ + criteria && Object.keys(criteria).length > 0 + ? 'WHERE ' + + Object.keys(criteria) + .map((crit) => (crit += '=?')) + .join(' AND ') + : '' + }`.trim(), + args: [...Object.values(obj), ...(criteria ? Object.values(criteria) : [])], + }); + +export const deleteFromRaw = < + TableMap extends TableScheme, + TableName extends keyof TableMap, + ColumnName extends keyof TableMap[TableName], +>( + keyspace: string, + table: TableName, + fields: '*' | ColumnName[], + criteria: { [key in ColumnName]?: TableMap[TableName][key] | string }, + extra?: string, + ): QueryBuild => ({ + query: `DELETE ${ + fields == '*' ? '' : fields.join(',') + } ${keyspace}.${table} ${ + criteria && Object.keys(criteria).length > 0 + ? 'WHERE ' + + Object.keys(criteria) + .map((crit) => crit + '=?') + .join(' AND ') + : '' + } ${extra || ''}`.trim(), + args: [...(criteria ? Object.values(criteria) : [])], + }); + +export type ColumnType = { type: keyof typeof types.dataTypes; test?: string }; + +export const createTableRaw = < + TableMap extends TableScheme, + F extends keyof TableMap, +>( + keyspace: string, + table: F, + createIfNotExists: boolean, + columns: { [key in keyof TableMap[F]]: ColumnType }, + partition: [keyof TableMap[F], keyof TableMap[F]] | keyof TableMap[F], + clustering?: (keyof TableMap[F])[], + ): QueryBuild => ({ + query: `CREATE TABLE${ + createIfNotExists ? ' IF NOT EXISTS' : '' + } ${keyspace}.${table} (${(Object.keys(columns) as (keyof TableMap[F])[]) + .map((a) => a + ' ' + columns[a].type) + .join(',')}, PRIMARY KEY (${ + partition instanceof Array ? '(' + partition.join(',') + ')' : partition + }${clustering ? `, ${clustering.join(',')}` : ''}))`, + args: [], + }); + +export const createIndexRaw = < + TableMap extends TableScheme, + Table extends keyof TableMap, + ColumnName extends keyof TableMap[Table], +>( + keyspace: string, + table: Table, + materialized_name: string, + column_to_index: ColumnName, + ): QueryBuild => ({ + query: `CREATE INDEX IF NOT EXISTS ${materialized_name} ON ${keyspace}.${table} (${column_to_index})`, + args: [], + }); + +export const createLocalIndexRaw = < + TableMap extends TableScheme, + Table extends keyof TableMap, + ColumnName extends keyof TableMap[Table], +>( + keyspace: string, + table: Table, + materialized_name: string, + primary_column: ColumnName, + column_to_index: ColumnName, + ): QueryBuild => ({ + query: `CREATE INDEX IF NOT EXISTS ${materialized_name} ON ${keyspace}.${table} ((${primary_column}), ${column_to_index})`, + args: [], + }); diff --git a/src/ScylloClient.ts b/src/ScylloClient.ts index 495d12a..313c109 100644 --- a/src/ScylloClient.ts +++ b/src/ScylloClient.ts @@ -2,12 +2,13 @@ import { LogMethod } from '@lvksh/logger'; import { Client, DseClientOptions as CassandraConfig, types } from 'cassandra-driver'; import { + createIndexRaw, + createLocalIndexRaw, createTableRaw, deleteFromRaw, insertIntoRaw, QueryBuild, - updateRaw, -} from './'; + updateRaw} from './'; import { selectFromRaw, selectOneFromRaw } from './QueryBuilder'; import { fromScyllo, ValidDataType } from './ScylloTranslator'; @@ -124,7 +125,7 @@ export class ScylloClient { select: '*' | ColumnName[], criteria?: { [key in keyof Tables[Table]]?: Tables[Table][key] | string }, extra?: string - ): Promise> { + ): Promise | undefined> { const query = selectOneFromRaw( this.keyspace, table, @@ -242,4 +243,35 @@ export class ScylloClient { `CREATE KEYSPACE IF NOT EXISTS ${keyspace} WITH replication = {'class':'${replicationClass}', 'replication_factor' : ${replicationFactor}};` ); } + + /** + * Create a Global Secondary Index + * + * https://docs.scylladb.com/using-scylla/secondary-indexes/ + */ + async createIndex( + table: Table, + materialized_name: string, + column_to_index: ColumnName + ): Promise { + const query = createIndexRaw(this.keyspace, table, materialized_name, column_to_index); + + return await this.query(query); + } + + /** + * Create a Local Secondary Index + * + * https://docs.scylladb.com/using-scylla/local-secondary-indexes/ + */ + async createLocalIndex
( + table: Table, + materialized_name: string, + primary_column: ColumnName, + column_to_index: ColumnName + ): Promise { + const query = createLocalIndexRaw(this.keyspace, table, materialized_name, primary_column, column_to_index); + + return await this.query(query); + } }