From 8b58da06ed8b18c4745ba5016f9082ec05d85135 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Thu, 6 Jul 2023 16:35:04 +0300 Subject: [PATCH 01/12] fix: format --- scripts/packages/format.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/packages/format.sh b/scripts/packages/format.sh index 0aefcb8..bc8deed 100755 --- a/scripts/packages/format.sh +++ b/scripts/packages/format.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -yarn prettier --write src/**/*.ts +yarn prettier --write "src/**/*.ts" From 2bc65dd9a7b1b947d15566204465554e09a947ff Mon Sep 17 00:00:00 2001 From: eisbilir Date: Thu, 6 Jul 2023 16:37:06 +0300 Subject: [PATCH 02/12] feat: add returning fields --- .../src/interfaces/Schema.ts | 1 + .../relational/relational.ts | 122 ++++++++++++++++-- .../src/query/QueryBuilder.ts | 1 + .../src/query/QueryRunner.ts | 20 ++- 4 files changed, 132 insertions(+), 12 deletions(-) diff --git a/clients/client-relational/src/interfaces/Schema.ts b/clients/client-relational/src/interfaces/Schema.ts index 21ae32f..ade6533 100644 --- a/clients/client-relational/src/interfaces/Schema.ts +++ b/clients/client-relational/src/interfaces/Schema.ts @@ -8,4 +8,5 @@ export type Schema> = { idSequence?: string; idTable?: string; columns: TableColumns; + returningFields?: string[]; }; diff --git a/clients/client-relational/src/models/data-access-layer/relational/relational.ts b/clients/client-relational/src/models/data-access-layer/relational/relational.ts index 68589eb..36d207b 100644 --- a/clients/client-relational/src/models/data-access-layer/relational/relational.ts +++ b/clients/client-relational/src/models/data-access-layer/relational/relational.ts @@ -310,6 +310,7 @@ export interface InsertQuery { idColumn?: string | undefined; idSequence?: string | undefined; idTable?: string | undefined; + returningFields: string[]; } export interface BulkInsertQuery { @@ -360,7 +361,13 @@ export interface RawQueryResult { } export interface InsertQueryResult { - lastInsertId: number; + insertResultType?: + | { $case: "lastInsertId"; lastInsertId: number } + | { $case: "row"; row: Row } + | { + $case: "affectedRows"; + affectedRows: number; + }; } export interface UpdateQueryResult { @@ -1362,6 +1369,7 @@ function createBaseInsertQuery(): InsertQuery { idColumn: undefined, idSequence: undefined, idTable: undefined, + returningFields: [], }; } @@ -1388,6 +1396,9 @@ export const InsertQuery = { if (message.idTable !== undefined) { writer.uint32(50).string(message.idTable); } + for (const v of message.returningFields) { + writer.uint32(58).string(v!); + } return writer; }, @@ -1441,6 +1452,13 @@ export const InsertQuery = { message.idTable = reader.string(); continue; + case 7: + if (tag !== 58) { + break; + } + + message.returningFields.push(reader.string()); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -1462,6 +1480,9 @@ export const InsertQuery = { ? String(object.idSequence) : undefined, idTable: isSet(object.idTable) ? String(object.idTable) : undefined, + returningFields: Array.isArray(object?.returningFields) + ? object.returningFields.map((e: any) => String(e)) + : [], }; }, @@ -1479,6 +1500,11 @@ export const InsertQuery = { message.idColumn !== undefined && (obj.idColumn = message.idColumn); message.idSequence !== undefined && (obj.idSequence = message.idSequence); message.idTable !== undefined && (obj.idTable = message.idTable); + if (message.returningFields) { + obj.returningFields = message.returningFields.map((e) => e); + } else { + obj.returningFields = []; + } return obj; }, @@ -1497,6 +1523,7 @@ export const InsertQuery = { message.idColumn = object.idColumn ?? undefined; message.idSequence = object.idSequence ?? undefined; message.idTable = object.idTable ?? undefined; + message.returningFields = object.returningFields?.map((e) => e) || []; return message; }, }; @@ -2385,7 +2412,7 @@ export const RawQueryResult = { }; function createBaseInsertQueryResult(): InsertQueryResult { - return { lastInsertId: 0 }; + return { insertResultType: undefined }; } export const InsertQueryResult = { @@ -2393,8 +2420,19 @@ export const InsertQueryResult = { message: InsertQueryResult, writer: _m0.Writer = _m0.Writer.create() ): _m0.Writer { - if (message.lastInsertId !== 0) { - writer.uint32(8).uint64(message.lastInsertId); + switch (message.insertResultType?.$case) { + case "lastInsertId": + writer.uint32(8).uint64(message.insertResultType.lastInsertId); + break; + case "row": + Row.encode( + message.insertResultType.row, + writer.uint32(18).fork() + ).ldelim(); + break; + case "affectedRows": + writer.uint32(24).uint64(message.insertResultType.affectedRows); + break; } return writer; }, @@ -2412,7 +2450,30 @@ export const InsertQueryResult = { break; } - message.lastInsertId = longToNumber(reader.uint64() as Long); + message.insertResultType = { + $case: "lastInsertId", + lastInsertId: longToNumber(reader.uint64() as Long), + }; + continue; + case 2: + if (tag !== 18) { + break; + } + + message.insertResultType = { + $case: "row", + row: Row.decode(reader, reader.uint32()), + }; + continue; + case 3: + if (tag !== 24) { + break; + } + + message.insertResultType = { + $case: "affectedRows", + affectedRows: longToNumber(reader.uint64() as Long), + }; continue; } if ((tag & 7) === 4 || tag === 0) { @@ -2425,16 +2486,26 @@ export const InsertQueryResult = { fromJSON(object: any): InsertQueryResult { return { - lastInsertId: isSet(object.lastInsertId) - ? Number(object.lastInsertId) - : 0, + insertResultType: isSet(object.lastInsertId) + ? { $case: "lastInsertId", lastInsertId: Number(object.lastInsertId) } + : isSet(object.row) + ? { $case: "row", row: Row.fromJSON(object.row) } + : isSet(object.affectedRows) + ? { $case: "affectedRows", affectedRows: Number(object.affectedRows) } + : undefined, }; }, toJSON(message: InsertQueryResult): unknown { const obj: any = {}; - message.lastInsertId !== undefined && - (obj.lastInsertId = Math.round(message.lastInsertId)); + message.insertResultType?.$case === "lastInsertId" && + (obj.lastInsertId = Math.round(message.insertResultType?.lastInsertId)); + message.insertResultType?.$case === "row" && + (obj.row = message.insertResultType?.row + ? Row.toJSON(message.insertResultType?.row) + : undefined); + message.insertResultType?.$case === "affectedRows" && + (obj.affectedRows = Math.round(message.insertResultType?.affectedRows)); return obj; }, @@ -2448,7 +2519,36 @@ export const InsertQueryResult = { object: I ): InsertQueryResult { const message = createBaseInsertQueryResult(); - message.lastInsertId = object.lastInsertId ?? 0; + if ( + object.insertResultType?.$case === "lastInsertId" && + object.insertResultType?.lastInsertId !== undefined && + object.insertResultType?.lastInsertId !== null + ) { + message.insertResultType = { + $case: "lastInsertId", + lastInsertId: object.insertResultType.lastInsertId, + }; + } + if ( + object.insertResultType?.$case === "row" && + object.insertResultType?.row !== undefined && + object.insertResultType?.row !== null + ) { + message.insertResultType = { + $case: "row", + row: Row.fromPartial(object.insertResultType.row), + }; + } + if ( + object.insertResultType?.$case === "affectedRows" && + object.insertResultType?.affectedRows !== undefined && + object.insertResultType?.affectedRows !== null + ) { + message.insertResultType = { + $case: "affectedRows", + affectedRows: object.insertResultType.affectedRows, + }; + } return message; }, }; diff --git a/clients/client-relational/src/query/QueryBuilder.ts b/clients/client-relational/src/query/QueryBuilder.ts index 7cf2bc4..d15f00e 100644 --- a/clients/client-relational/src/query/QueryBuilder.ts +++ b/clients/client-relational/src/query/QueryBuilder.ts @@ -130,6 +130,7 @@ export class QueryBuilder> extends BaseQuery { idTable: this.schema.tableName, idColumn: this.schema.idColumn ?? undefined, idSequence: this.schema.idSequence ?? undefined, + returningFields: this.schema.returningFields ?? [], }, }, }; diff --git a/clients/client-relational/src/query/QueryRunner.ts b/clients/client-relational/src/query/QueryRunner.ts index 77261cf..c612e16 100644 --- a/clients/client-relational/src/query/QueryRunner.ts +++ b/clients/client-relational/src/query/QueryRunner.ts @@ -137,7 +137,25 @@ export class QueryRunner { }), }; case "insertResult": - return { lastInsertId: queryResponse.result.insertResult.lastInsertId }; + switch (queryResponse.result.insertResult.insertResultType?.$case) { + case "affectedRows": + return { + affectedRows: + queryResponse.result.insertResult.insertResultType.affectedRows, + }; + case "lastInsertId": + return { + lastInsertId: + queryResponse.result.insertResult.insertResultType.lastInsertId, + }; + case "row": + return { + rows: [queryResponse.result.insertResult.insertResultType.row], + }; + default: + throw new Error("Unexpected query insert result"); + } + case "updateResult": return { affectedRows: queryResponse.result.updateResult.affectedRows }; case "deleteResult": From 10cc220d41f7f6290b753d6c9fa259759602c49b Mon Sep 17 00:00:00 2001 From: eisbilir Date: Thu, 6 Jul 2023 22:19:29 +0300 Subject: [PATCH 03/12] fix: update gen-stubs --- clients/client-relational/bin/gen-stubs.js | 1 + domains/domain-acl/bin/gen-stubs.js | 1 + domains/domain-challenge/bin/gen-stubs.js | 1 + lib/lib-common/bin/gen-stubs.js | 1 + lib/lib-common/src/models/google/protobuf/empty.ts | 2 ++ lib/lib-common/src/models/google/protobuf/struct.ts | 4 ++-- lib/lib-common/src/models/google/protobuf/timestamp.ts | 10 +--------- 7 files changed, 9 insertions(+), 11 deletions(-) diff --git a/clients/client-relational/bin/gen-stubs.js b/clients/client-relational/bin/gen-stubs.js index e66d758..2bf8f75 100644 --- a/clients/client-relational/bin/gen-stubs.js +++ b/clients/client-relational/bin/gen-stubs.js @@ -35,6 +35,7 @@ const protoConfig = [ `--ts_proto_opt=Mgoogle/protobuf/struct.proto=@topcoder-framework/lib-common`, `--ts_proto_opt=Mgoogle/protobuf/timestamp.proto=@topcoder-framework/lib-common`, `--proto_path ${PROTO_DIR} ${PROTO_DIR}/data-access-layer/relational/*.proto`, + `--experimental_allow_proto3_optional`, ]; // https://github.com/stephenh/ts-proto#usage diff --git a/domains/domain-acl/bin/gen-stubs.js b/domains/domain-acl/bin/gen-stubs.js index d186b3e..77c7047 100644 --- a/domains/domain-acl/bin/gen-stubs.js +++ b/domains/domain-acl/bin/gen-stubs.js @@ -37,6 +37,7 @@ const protoConfig = [ `--ts_proto_opt=Mgoogle/protobuf/empty.proto=@topcoder-framework/lib-common`, `--proto_path ${PROTO_DIR} ${PROTO_DIR}/domain-layer/legacy/*.proto`, `--proto_path ${PROTO_DIR} ${PROTO_DIR}/domain-layer/legacy/services/*.proto`, + `--experimental_allow_proto3_optional`, ]; // https://github.com/stephenh/ts-proto#usage diff --git a/domains/domain-challenge/bin/gen-stubs.js b/domains/domain-challenge/bin/gen-stubs.js index 835f2d2..7bc543a 100644 --- a/domains/domain-challenge/bin/gen-stubs.js +++ b/domains/domain-challenge/bin/gen-stubs.js @@ -38,6 +38,7 @@ const protoConfig = [ `--ts_proto_opt=Mgoogle/protobuf/empty.proto=@topcoder-framework/lib-common`, `--proto_path ${PROTO_DIR} ${PROTO_DIR}/domain-layer/challenge/*.proto`, `--proto_path ${PROTO_DIR} ${PROTO_DIR}/domain-layer/challenge/services/*.proto`, + `--experimental_allow_proto3_optional`, ]; // https://github.com/stephenh/ts-proto#usage diff --git a/lib/lib-common/bin/gen-stubs.js b/lib/lib-common/bin/gen-stubs.js index 3966fd8..301c49c 100644 --- a/lib/lib-common/bin/gen-stubs.js +++ b/lib/lib-common/bin/gen-stubs.js @@ -34,6 +34,7 @@ const protoConfig = [ `--ts_proto_opt=outputClientImpl=true`, `--ts_proto_out=${MODEL_DIR}`, `--proto_path ${PROTO_DIR} ${PROTO_DIR}/common/*.proto`, + `--experimental_allow_proto3_optional`, ]; // https://github.com/stephenh/ts-proto#usage diff --git a/lib/lib-common/src/models/google/protobuf/empty.ts b/lib/lib-common/src/models/google/protobuf/empty.ts index 4414abb..5187163 100644 --- a/lib/lib-common/src/models/google/protobuf/empty.ts +++ b/lib/lib-common/src/models/google/protobuf/empty.ts @@ -9,6 +9,8 @@ import _m0 from "protobufjs/minimal"; * service Foo { * rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); * } + * + * The JSON representation for `Empty` is empty JSON object `{}`. */ export interface Empty {} diff --git a/lib/lib-common/src/models/google/protobuf/struct.ts b/lib/lib-common/src/models/google/protobuf/struct.ts index 3385280..c2ebeca 100644 --- a/lib/lib-common/src/models/google/protobuf/struct.ts +++ b/lib/lib-common/src/models/google/protobuf/struct.ts @@ -58,8 +58,8 @@ export interface Struct_FieldsEntry { /** * `Value` represents a dynamically typed value which can be either * null, a number, a string, a boolean, a recursive struct value, or a - * list of values. A producer of value is expected to set one of these - * variants. Absence of any variant indicates an error. + * list of values. A producer of value is expected to set one of that + * variants, absence of any variant indicates an error. * * The JSON representation for `Value` is JSON value. */ diff --git a/lib/lib-common/src/models/google/protobuf/timestamp.ts b/lib/lib-common/src/models/google/protobuf/timestamp.ts index e4a5adf..66f8734 100644 --- a/lib/lib-common/src/models/google/protobuf/timestamp.ts +++ b/lib/lib-common/src/models/google/protobuf/timestamp.ts @@ -53,15 +53,7 @@ import _m0 from "protobufjs/minimal"; * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) * .setNanos((int) ((millis % 1000) * 1000000)).build(); * - * Example 5: Compute Timestamp from Java `Instant.now()`. - * - * Instant now = Instant.now(); - * - * Timestamp timestamp = - * Timestamp.newBuilder().setSeconds(now.getEpochSecond()) - * .setNanos(now.getNano()).build(); - * - * Example 6: Compute Timestamp from current time in Python. + * Example 5: Compute Timestamp from current time in Python. * * timestamp = Timestamp() * timestamp.GetCurrentTime() From 8502eda0961be39a1fc9be9153dc0a0ea5b4b9ae Mon Sep 17 00:00:00 2001 From: eisbilir Date: Thu, 6 Jul 2023 23:19:56 +0300 Subject: [PATCH 04/12] feat: add RowValueType type --- .../src/query/QueryRunner.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/clients/client-relational/src/query/QueryRunner.ts b/clients/client-relational/src/query/QueryRunner.ts index c612e16..7330fcc 100644 --- a/clients/client-relational/src/query/QueryRunner.ts +++ b/clients/client-relational/src/query/QueryRunner.ts @@ -14,8 +14,10 @@ export interface Transaction { rollback(): void; } +export type RowValueType = string | number | boolean | null; + export interface QueryResult { - rows?: { [key: string]: any }[]; + rows?: { [key: string]: RowValueType }[]; lastInsertId?: number; affectedRows?: number; } @@ -81,7 +83,10 @@ export class QueryRunner { }; } - private unwrapValue(value: RelationalValue): number | string | boolean { + private unwrapValue(value: RelationalValue): RowValueType { + if (_.isUndefined(value.value)) { + return null; + } switch (value.value?.$case) { case "intValue": return value.value.intValue; @@ -111,7 +116,7 @@ export class QueryRunner { switch (queryResponse.result?.$case) { case "rawResult": { const rows = queryResponse.result.rawResult?.rows.map(({ values }) => { - const retObject: { [key: string]: number | string | boolean } = {}; + const retObject: { [key: string]: RowValueType } = {}; Object.entries(values).forEach(([key, value]) => { retObject[key] = this.unwrapValue(value); }); @@ -125,7 +130,7 @@ export class QueryRunner { case "selectResult": return { rows: queryResponse.result.selectResult.rows.map(({ values }) => { - const retObject: { [key: string]: number | string | boolean } = {}; + const retObject: { [key: string]: RowValueType } = {}; column.forEach((col) => { retObject[_.camelCase(col.name)] = this.unwrapValue( @@ -150,7 +155,12 @@ export class QueryRunner { }; case "row": return { - rows: [queryResponse.result.insertResult.insertResultType.row], + rows: [ + _.mapValues( + queryResponse.result.insertResult.insertResultType.row.values, + (v) => this.unwrapValue(v) + ), + ], }; default: throw new Error("Unexpected query insert result"); From 71ac569b2f0294c648d3d0b6870bfc5b9e6857c0 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Sun, 9 Jul 2023 22:52:57 +0300 Subject: [PATCH 05/12] feat: add support for nested where --- .../relational/relational.ts | 340 +++++++++++++++++- .../src/query/QueryBuilder.ts | 194 +++++----- 2 files changed, 432 insertions(+), 102 deletions(-) diff --git a/clients/client-relational/src/models/data-access-layer/relational/relational.ts b/clients/client-relational/src/models/data-access-layer/relational/relational.ts index 36d207b..6616603 100644 --- a/clients/client-relational/src/models/data-access-layer/relational/relational.ts +++ b/clients/client-relational/src/models/data-access-layer/relational/relational.ts @@ -266,12 +266,30 @@ export interface Column { type?: ColumnType | undefined; } -export interface WhereCriteria { +export interface WhereCondition { operator: Operator; key: string; value?: Value; } +export interface AndWhere { + where: WhereCondition[]; +} + +export interface OrWhere { + where: WhereCondition[]; +} + +export interface WhereCriteria { + whereType?: + | { $case: "condition"; condition: WhereCondition } + | { $case: "and"; and: AndWhere } + | { + $case: "or"; + or: OrWhere; + }; +} + export interface RawQuery { query: string; } @@ -759,13 +777,13 @@ export const Column = { }, }; -function createBaseWhereCriteria(): WhereCriteria { +function createBaseWhereCondition(): WhereCondition { return { operator: 0, key: "", value: undefined }; } -export const WhereCriteria = { +export const WhereCondition = { encode( - message: WhereCriteria, + message: WhereCondition, writer: _m0.Writer = _m0.Writer.create() ): _m0.Writer { if (message.operator !== 0) { @@ -780,11 +798,11 @@ export const WhereCriteria = { return writer; }, - decode(input: _m0.Reader | Uint8Array, length?: number): WhereCriteria { + decode(input: _m0.Reader | Uint8Array, length?: number): WhereCondition { const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseWhereCriteria(); + const message = createBaseWhereCondition(); while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { @@ -818,7 +836,7 @@ export const WhereCriteria = { return message; }, - fromJSON(object: any): WhereCriteria { + fromJSON(object: any): WhereCondition { return { operator: isSet(object.operator) ? operatorFromJSON(object.operator) : 0, key: isSet(object.key) ? String(object.key) : "", @@ -826,7 +844,7 @@ export const WhereCriteria = { }; }, - toJSON(message: WhereCriteria): unknown { + toJSON(message: WhereCondition): unknown { const obj: any = {}; message.operator !== undefined && (obj.operator = operatorToJSON(message.operator)); @@ -836,16 +854,16 @@ export const WhereCriteria = { return obj; }, - create, I>>( + create, I>>( base?: I - ): WhereCriteria { - return WhereCriteria.fromPartial(base ?? {}); + ): WhereCondition { + return WhereCondition.fromPartial(base ?? {}); }, - fromPartial, I>>( + fromPartial, I>>( object: I - ): WhereCriteria { - const message = createBaseWhereCriteria(); + ): WhereCondition { + const message = createBaseWhereCondition(); message.operator = object.operator ?? 0; message.key = object.key ?? ""; message.value = @@ -856,6 +874,300 @@ export const WhereCriteria = { }, }; +function createBaseAndWhere(): AndWhere { + return { where: [] }; +} + +export const AndWhere = { + encode( + message: AndWhere, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + for (const v of message.where) { + WhereCondition.encode(v!, writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): AndWhere { + const reader = + input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseAndWhere(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.where.push(WhereCondition.decode(reader, reader.uint32())); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): AndWhere { + return { + where: Array.isArray(object?.where) + ? object.where.map((e: any) => WhereCondition.fromJSON(e)) + : [], + }; + }, + + toJSON(message: AndWhere): unknown { + const obj: any = {}; + if (message.where) { + obj.where = message.where.map((e) => + e ? WhereCondition.toJSON(e) : undefined + ); + } else { + obj.where = []; + } + return obj; + }, + + create, I>>(base?: I): AndWhere { + return AndWhere.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): AndWhere { + const message = createBaseAndWhere(); + message.where = + object.where?.map((e) => WhereCondition.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseOrWhere(): OrWhere { + return { where: [] }; +} + +export const OrWhere = { + encode( + message: OrWhere, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + for (const v of message.where) { + WhereCondition.encode(v!, writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): OrWhere { + const reader = + input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseOrWhere(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.where.push(WhereCondition.decode(reader, reader.uint32())); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): OrWhere { + return { + where: Array.isArray(object?.where) + ? object.where.map((e: any) => WhereCondition.fromJSON(e)) + : [], + }; + }, + + toJSON(message: OrWhere): unknown { + const obj: any = {}; + if (message.where) { + obj.where = message.where.map((e) => + e ? WhereCondition.toJSON(e) : undefined + ); + } else { + obj.where = []; + } + return obj; + }, + + create, I>>(base?: I): OrWhere { + return OrWhere.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): OrWhere { + const message = createBaseOrWhere(); + message.where = + object.where?.map((e) => WhereCondition.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseWhereCriteria(): WhereCriteria { + return { whereType: undefined }; +} + +export const WhereCriteria = { + encode( + message: WhereCriteria, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + switch (message.whereType?.$case) { + case "condition": + WhereCondition.encode( + message.whereType.condition, + writer.uint32(10).fork() + ).ldelim(); + break; + case "and": + AndWhere.encode( + message.whereType.and, + writer.uint32(18).fork() + ).ldelim(); + break; + case "or": + OrWhere.encode(message.whereType.or, writer.uint32(26).fork()).ldelim(); + break; + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): WhereCriteria { + const reader = + input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseWhereCriteria(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.whereType = { + $case: "condition", + condition: WhereCondition.decode(reader, reader.uint32()), + }; + continue; + case 2: + if (tag !== 18) { + break; + } + + message.whereType = { + $case: "and", + and: AndWhere.decode(reader, reader.uint32()), + }; + continue; + case 3: + if (tag !== 26) { + break; + } + + message.whereType = { + $case: "or", + or: OrWhere.decode(reader, reader.uint32()), + }; + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): WhereCriteria { + return { + whereType: isSet(object.condition) + ? { + $case: "condition", + condition: WhereCondition.fromJSON(object.condition), + } + : isSet(object.and) + ? { $case: "and", and: AndWhere.fromJSON(object.and) } + : isSet(object.or) + ? { $case: "or", or: OrWhere.fromJSON(object.or) } + : undefined, + }; + }, + + toJSON(message: WhereCriteria): unknown { + const obj: any = {}; + message.whereType?.$case === "condition" && + (obj.condition = message.whereType?.condition + ? WhereCondition.toJSON(message.whereType?.condition) + : undefined); + message.whereType?.$case === "and" && + (obj.and = message.whereType?.and + ? AndWhere.toJSON(message.whereType?.and) + : undefined); + message.whereType?.$case === "or" && + (obj.or = message.whereType?.or + ? OrWhere.toJSON(message.whereType?.or) + : undefined); + return obj; + }, + + create, I>>( + base?: I + ): WhereCriteria { + return WhereCriteria.fromPartial(base ?? {}); + }, + + fromPartial, I>>( + object: I + ): WhereCriteria { + const message = createBaseWhereCriteria(); + if ( + object.whereType?.$case === "condition" && + object.whereType?.condition !== undefined && + object.whereType?.condition !== null + ) { + message.whereType = { + $case: "condition", + condition: WhereCondition.fromPartial(object.whereType.condition), + }; + } + if ( + object.whereType?.$case === "and" && + object.whereType?.and !== undefined && + object.whereType?.and !== null + ) { + message.whereType = { + $case: "and", + and: AndWhere.fromPartial(object.whereType.and), + }; + } + if ( + object.whereType?.$case === "or" && + object.whereType?.or !== undefined && + object.whereType?.or !== null + ) { + message.whereType = { + $case: "or", + or: OrWhere.fromPartial(object.whereType.or), + }; + } + return message; + }, +}; + function createBaseRawQuery(): RawQuery { return { query: "" }; } diff --git a/clients/client-relational/src/query/QueryBuilder.ts b/clients/client-relational/src/query/QueryBuilder.ts index d15f00e..771110c 100644 --- a/clients/client-relational/src/query/QueryBuilder.ts +++ b/clients/client-relational/src/query/QueryBuilder.ts @@ -9,13 +9,22 @@ import { Operator, Query, Value as RelationalValue, + WhereCriteria, + WhereCondition as Wheres, } from "../models/data-access-layer/relational/relational"; import { BaseQuery } from "./BaseQuery"; +import _ from "lodash"; -export type WhereCondition = - | [column: TableColumn, operator: Operator, value: RelationalValue] - | ScanCriteria[]; +type Condition = [ + column: TableColumn, + operator: Operator, + value: RelationalValue +]; + +type ConditionGroup = [group: "and" | "or", conditions: Condition[]]; + +export type WhereCondition = Condition | ScanCriteria[] | ConditionGroup; interface Build { build: () => Query; @@ -232,6 +241,14 @@ export class QueryBuilder> extends BaseQuery { }; } + public and(conditions: Condition[]): ConditionGroup { + return ["and", conditions]; + } + + public or(conditions: Condition[]): ConditionGroup { + return ["or", conditions]; + } + // private orderBy(column: TableColumn, direction: "asc" | "desc") {} private where(...inputs: WhereCondition): Where { @@ -245,37 +262,16 @@ export class QueryBuilder> extends BaseQuery { if (this.isScanCriteria(inputs[0])) { for (const input of inputs as ScanCriteria[]) { - this.#query.query.select.where.push({ - key: this.schema.columns[input.key].name, - // TODO: map from @topcoder-framework/lib-common Operator to RelationalOperator - operator: - input.operator === LibCommonOperator.OPERATOR_NOT_EQUAL - ? Operator.OPERATOR_NOT_EQUAL - : Operator.OPERATOR_EQUAL, - value: this.toRelationalValue(input.key, input.value), - }); + this.#query.query.select.where.push(this.buildForScanCriteria(input)); } - return { - andWhere: this.where.bind(this), - // orderBy: this.orderBy.bind(this), - limit: this.limit.bind(this), - offset: this.offset.bind(this), - build: this.build.bind(this), - }; + } else if (this.isConditionGroup(inputs)) { + this.#query.query.select.where.push(this.buildForConditionGroup(inputs)); + } else { + this.#query.query.select.where.push( + this.buildForCondition(inputs as Condition) + ); } - const [column, operator, value] = inputs as [ - TableColumn, - Operator, - RelationalValue - ]; - - this.#query.query.select.where.push({ - key: column.name, - operator, - value, - }); - return { andWhere: this.where.bind(this), // orderBy: this.orderBy.bind(this), @@ -293,78 +289,91 @@ export class QueryBuilder> extends BaseQuery { if (this.#query?.query?.$case === "update") { if (this.isScanCriteria(inputs[0])) { for (const input of inputs as ScanCriteria[]) { - this.#query.query.update.where.push({ - key: this.schema.columns[input.key].name, - operator: - input.operator === LibCommonOperator.OPERATOR_NOT_EQUAL - ? Operator.OPERATOR_NOT_EQUAL - : Operator.OPERATOR_EQUAL, - value: this.toRelationalValue(input.key, input.value), - }); + this.#query.query.update.where.push(this.buildForScanCriteria(input)); } - return { - andWhere: this.whereForModify.bind(this), - build: this.build.bind(this), - }; + } else if (this.isConditionGroup(inputs)) { + this.#query.query.update.where.push( + this.buildForConditionGroup(inputs) + ); + } else { + this.#query.query.update.where.push( + this.buildForCondition(inputs as Condition) + ); } - - const [column, operator, value] = inputs as [ - TableColumn, - Operator, - RelationalValue - ]; - - this.#query.query.update.where.push({ - key: column.name, - operator, - value, - }); - - return { - andWhere: this.whereForModify.bind(this), - build: this.build.bind(this), - }; - } - - if (this.#query?.query?.$case === "delete") { + } else if (this.#query?.query?.$case === "delete") { if (this.isScanCriteria(inputs[0])) { for (const input of inputs as ScanCriteria[]) { - this.#query.query.delete.where.push({ - key: this.schema.columns[input.key].name, - operator: - input.operator === LibCommonOperator.OPERATOR_NOT_EQUAL - ? Operator.OPERATOR_NOT_EQUAL - : Operator.OPERATOR_EQUAL, - value: this.toRelationalValue(input.key, input.value), - }); + this.#query.query.delete.where.push(this.buildForScanCriteria(input)); } - return { - andWhere: this.whereForModify.bind(this), - build: this.build.bind(this), - }; + } else if (this.isConditionGroup(inputs)) { + this.#query.query.delete.where.push( + this.buildForConditionGroup(inputs) + ); + } else { + this.#query.query.delete.where.push( + this.buildForCondition(inputs as Condition) + ); } + } - const [column, operator, value] = inputs as [ - TableColumn, - Operator, - RelationalValue - ]; + return { + andWhere: this.whereForModify.bind(this), + build: this.build.bind(this), + }; + } + + private buildForConditionGroup( + conditionGroup: ConditionGroup + ): Partial { + const [group, conditions] = conditionGroup; - this.#query.query.delete.where.push({ + const wheres: Wheres[] = _.map(conditions, (condition) => { + const [column, operator, value] = condition; + return { key: column.name, operator, value, - }); - - return { - andWhere: this.whereForModify.bind(this), - build: this.build.bind(this), }; + }); + + const whereType: Partial = {}; + if (group === "and") { + whereType.whereType = { $case: "and", and: { where: wheres } }; + } else { + whereType.whereType = { $case: "or", or: { where: wheres } }; } + return whereType; + } + + private buildForCondition(condition: Condition): Partial { + const [column, operator, value] = condition; + return { - andWhere: this.whereForModify.bind(this), - build: this.build.bind(this), + whereType: { + $case: "condition", + condition: { + key: column.name, + operator, + value, + }, + }, + }; + } + + private buildForScanCriteria(criteria: ScanCriteria): Partial { + return { + whereType: { + $case: "condition", + condition: { + key: this.schema.columns[criteria.key].name, + operator: + criteria.operator === LibCommonOperator.OPERATOR_NOT_EQUAL + ? Operator.OPERATOR_NOT_EQUAL + : Operator.OPERATOR_EQUAL, + value: this.toRelationalValue(criteria.key, criteria.value), + }, + }, }; } @@ -408,6 +417,15 @@ export class QueryBuilder> extends BaseQuery { ); } + private isConditionGroup(criteria: unknown): criteria is ConditionGroup { + return ( + criteria != null && + typeof criteria === "object" && + typeof (criteria as ConditionGroup)[0] === "string" && + typeof (criteria as ConditionGroup)[1] === "object" + ); + } + private isWKTValue(value: unknown): value is Value { return ( typeof value === "object" && From bbe50e9746a7a2ade648295334c9fbecd0ce9350 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Mon, 10 Jul 2023 01:06:09 +0300 Subject: [PATCH 06/12] fix: condition group --- .../relational/relational.ts | 24 +++++++++---------- .../src/query/QueryBuilder.ts | 21 ++++++++++------ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/clients/client-relational/src/models/data-access-layer/relational/relational.ts b/clients/client-relational/src/models/data-access-layer/relational/relational.ts index 6616603..d936411 100644 --- a/clients/client-relational/src/models/data-access-layer/relational/relational.ts +++ b/clients/client-relational/src/models/data-access-layer/relational/relational.ts @@ -273,11 +273,11 @@ export interface WhereCondition { } export interface AndWhere { - where: WhereCondition[]; + where: WhereCriteria[]; } export interface OrWhere { - where: WhereCondition[]; + where: WhereCriteria[]; } export interface WhereCriteria { @@ -884,7 +884,7 @@ export const AndWhere = { writer: _m0.Writer = _m0.Writer.create() ): _m0.Writer { for (const v of message.where) { - WhereCondition.encode(v!, writer.uint32(10).fork()).ldelim(); + WhereCriteria.encode(v!, writer.uint32(10).fork()).ldelim(); } return writer; }, @@ -902,7 +902,7 @@ export const AndWhere = { break; } - message.where.push(WhereCondition.decode(reader, reader.uint32())); + message.where.push(WhereCriteria.decode(reader, reader.uint32())); continue; } if ((tag & 7) === 4 || tag === 0) { @@ -916,7 +916,7 @@ export const AndWhere = { fromJSON(object: any): AndWhere { return { where: Array.isArray(object?.where) - ? object.where.map((e: any) => WhereCondition.fromJSON(e)) + ? object.where.map((e: any) => WhereCriteria.fromJSON(e)) : [], }; }, @@ -925,7 +925,7 @@ export const AndWhere = { const obj: any = {}; if (message.where) { obj.where = message.where.map((e) => - e ? WhereCondition.toJSON(e) : undefined + e ? WhereCriteria.toJSON(e) : undefined ); } else { obj.where = []; @@ -940,7 +940,7 @@ export const AndWhere = { fromPartial, I>>(object: I): AndWhere { const message = createBaseAndWhere(); message.where = - object.where?.map((e) => WhereCondition.fromPartial(e)) || []; + object.where?.map((e) => WhereCriteria.fromPartial(e)) || []; return message; }, }; @@ -955,7 +955,7 @@ export const OrWhere = { writer: _m0.Writer = _m0.Writer.create() ): _m0.Writer { for (const v of message.where) { - WhereCondition.encode(v!, writer.uint32(10).fork()).ldelim(); + WhereCriteria.encode(v!, writer.uint32(10).fork()).ldelim(); } return writer; }, @@ -973,7 +973,7 @@ export const OrWhere = { break; } - message.where.push(WhereCondition.decode(reader, reader.uint32())); + message.where.push(WhereCriteria.decode(reader, reader.uint32())); continue; } if ((tag & 7) === 4 || tag === 0) { @@ -987,7 +987,7 @@ export const OrWhere = { fromJSON(object: any): OrWhere { return { where: Array.isArray(object?.where) - ? object.where.map((e: any) => WhereCondition.fromJSON(e)) + ? object.where.map((e: any) => WhereCriteria.fromJSON(e)) : [], }; }, @@ -996,7 +996,7 @@ export const OrWhere = { const obj: any = {}; if (message.where) { obj.where = message.where.map((e) => - e ? WhereCondition.toJSON(e) : undefined + e ? WhereCriteria.toJSON(e) : undefined ); } else { obj.where = []; @@ -1011,7 +1011,7 @@ export const OrWhere = { fromPartial, I>>(object: I): OrWhere { const message = createBaseOrWhere(); message.where = - object.where?.map((e) => WhereCondition.fromPartial(e)) || []; + object.where?.map((e) => WhereCriteria.fromPartial(e)) || []; return message; }, }; diff --git a/clients/client-relational/src/query/QueryBuilder.ts b/clients/client-relational/src/query/QueryBuilder.ts index 771110c..872f206 100644 --- a/clients/client-relational/src/query/QueryBuilder.ts +++ b/clients/client-relational/src/query/QueryBuilder.ts @@ -10,7 +10,6 @@ import { Query, Value as RelationalValue, WhereCriteria, - WhereCondition as Wheres, } from "../models/data-access-layer/relational/relational"; import { BaseQuery } from "./BaseQuery"; @@ -22,7 +21,7 @@ type Condition = [ value: RelationalValue ]; -type ConditionGroup = [group: "and" | "or", conditions: Condition[]]; +type ConditionGroup = [group: "and" | "or", conditions: WhereCondition[]]; export type WhereCondition = Condition | ScanCriteria[] | ConditionGroup; @@ -327,12 +326,20 @@ export class QueryBuilder> extends BaseQuery { ): Partial { const [group, conditions] = conditionGroup; - const wheres: Wheres[] = _.map(conditions, (condition) => { - const [column, operator, value] = condition; + const wheres: WhereCriteria[] = _.map(conditions, (condition) => { + if (this.isConditionGroup(condition)) { + return this.buildForConditionGroup(condition); + } + const [column, operator, value] = condition as Condition; return { - key: column.name, - operator, - value, + whereType: { + $case: "condition", + condition: { + key: column.name, + operator, + value, + }, + }, }; }); From 4548c30f05a430f7b1060c0dc8ab6ec28158d641 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Mon, 10 Jul 2023 03:37:24 +0300 Subject: [PATCH 07/12] feat: update field name --- .../relational/relational.ts | 40 +++---- .../src/query/QueryBuilder.ts | 109 ++++++++++-------- 2 files changed, 77 insertions(+), 72 deletions(-) diff --git a/clients/client-relational/src/models/data-access-layer/relational/relational.ts b/clients/client-relational/src/models/data-access-layer/relational/relational.ts index d936411..b21d6b3 100644 --- a/clients/client-relational/src/models/data-access-layer/relational/relational.ts +++ b/clients/client-relational/src/models/data-access-layer/relational/relational.ts @@ -266,7 +266,7 @@ export interface Column { type?: ColumnType | undefined; } -export interface WhereCondition { +export interface Condition { operator: Operator; key: string; value?: Value; @@ -282,7 +282,7 @@ export interface OrWhere { export interface WhereCriteria { whereType?: - | { $case: "condition"; condition: WhereCondition } + | { $case: "condition"; condition: Condition } | { $case: "and"; and: AndWhere } | { $case: "or"; @@ -777,13 +777,13 @@ export const Column = { }, }; -function createBaseWhereCondition(): WhereCondition { +function createBaseCondition(): Condition { return { operator: 0, key: "", value: undefined }; } -export const WhereCondition = { +export const Condition = { encode( - message: WhereCondition, + message: Condition, writer: _m0.Writer = _m0.Writer.create() ): _m0.Writer { if (message.operator !== 0) { @@ -798,11 +798,11 @@ export const WhereCondition = { return writer; }, - decode(input: _m0.Reader | Uint8Array, length?: number): WhereCondition { + decode(input: _m0.Reader | Uint8Array, length?: number): Condition { const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseWhereCondition(); + const message = createBaseCondition(); while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { @@ -836,7 +836,7 @@ export const WhereCondition = { return message; }, - fromJSON(object: any): WhereCondition { + fromJSON(object: any): Condition { return { operator: isSet(object.operator) ? operatorFromJSON(object.operator) : 0, key: isSet(object.key) ? String(object.key) : "", @@ -844,7 +844,7 @@ export const WhereCondition = { }; }, - toJSON(message: WhereCondition): unknown { + toJSON(message: Condition): unknown { const obj: any = {}; message.operator !== undefined && (obj.operator = operatorToJSON(message.operator)); @@ -854,16 +854,14 @@ export const WhereCondition = { return obj; }, - create, I>>( - base?: I - ): WhereCondition { - return WhereCondition.fromPartial(base ?? {}); + create, I>>(base?: I): Condition { + return Condition.fromPartial(base ?? {}); }, - fromPartial, I>>( + fromPartial, I>>( object: I - ): WhereCondition { - const message = createBaseWhereCondition(); + ): Condition { + const message = createBaseCondition(); message.operator = object.operator ?? 0; message.key = object.key ?? ""; message.value = @@ -1027,7 +1025,7 @@ export const WhereCriteria = { ): _m0.Writer { switch (message.whereType?.$case) { case "condition": - WhereCondition.encode( + Condition.encode( message.whereType.condition, writer.uint32(10).fork() ).ldelim(); @@ -1060,7 +1058,7 @@ export const WhereCriteria = { message.whereType = { $case: "condition", - condition: WhereCondition.decode(reader, reader.uint32()), + condition: Condition.decode(reader, reader.uint32()), }; continue; case 2: @@ -1097,7 +1095,7 @@ export const WhereCriteria = { whereType: isSet(object.condition) ? { $case: "condition", - condition: WhereCondition.fromJSON(object.condition), + condition: Condition.fromJSON(object.condition), } : isSet(object.and) ? { $case: "and", and: AndWhere.fromJSON(object.and) } @@ -1111,7 +1109,7 @@ export const WhereCriteria = { const obj: any = {}; message.whereType?.$case === "condition" && (obj.condition = message.whereType?.condition - ? WhereCondition.toJSON(message.whereType?.condition) + ? Condition.toJSON(message.whereType?.condition) : undefined); message.whereType?.$case === "and" && (obj.and = message.whereType?.and @@ -1141,7 +1139,7 @@ export const WhereCriteria = { ) { message.whereType = { $case: "condition", - condition: WhereCondition.fromPartial(object.whereType.condition), + condition: Condition.fromPartial(object.whereType.condition), }; } if ( diff --git a/clients/client-relational/src/query/QueryBuilder.ts b/clients/client-relational/src/query/QueryBuilder.ts index 872f206..beaa306 100644 --- a/clients/client-relational/src/query/QueryBuilder.ts +++ b/clients/client-relational/src/query/QueryBuilder.ts @@ -15,15 +15,18 @@ import { import { BaseQuery } from "./BaseQuery"; import _ from "lodash"; -type Condition = [ - column: TableColumn, - operator: Operator, - value: RelationalValue -]; +interface Condition { + column: TableColumn; + operator: Operator; + value: RelationalValue; +} -type ConditionGroup = [group: "and" | "or", conditions: WhereCondition[]]; +interface ConditionGroup { + group: "and" | "or"; + conditions: WhereCondition; +} -export type WhereCondition = Condition | ScanCriteria[] | ConditionGroup; +export type WhereCondition = Condition[] | ScanCriteria[] | ConditionGroup[]; interface Build { build: () => Query; @@ -58,6 +61,14 @@ interface Delete { where: (...inputs: WhereCondition) => WhereForModify; } +export function and(...conditions: Condition[]): ConditionGroup { + return { group: "and", conditions }; +} + +export function or(...conditions: Condition[]): ConditionGroup { + return { group: "or", conditions }; +} + // eslint-disable-next-line @typescript-eslint/no-explicit-any export class QueryBuilder> extends BaseQuery { #query: Query | null = null; @@ -240,14 +251,6 @@ export class QueryBuilder> extends BaseQuery { }; } - public and(conditions: Condition[]): ConditionGroup { - return ["and", conditions]; - } - - public or(conditions: Condition[]): ConditionGroup { - return ["or", conditions]; - } - // private orderBy(column: TableColumn, direction: "asc" | "desc") {} private where(...inputs: WhereCondition): Where { @@ -259,16 +262,16 @@ export class QueryBuilder> extends BaseQuery { throw new Error("Where requires at least one argument"); } - if (this.isScanCriteria(inputs[0])) { - for (const input of inputs as ScanCriteria[]) { - this.#query.query.select.where.push(this.buildForScanCriteria(input)); + for (const input of inputs) { + if (this.isScanCriteria(input)) { + for (const scan of inputs as ScanCriteria[]) { + this.#query.query.select.where.push(this.buildForScanCriteria(scan)); + } + } else if (this.isConditionGroup(input)) { + this.#query.query.select.where.push(this.buildForConditionGroup(input)); + } else { + this.#query.query.select.where.push(this.buildForCondition(input)); } - } else if (this.isConditionGroup(inputs)) { - this.#query.query.select.where.push(this.buildForConditionGroup(inputs)); - } else { - this.#query.query.select.where.push( - this.buildForCondition(inputs as Condition) - ); } return { @@ -286,32 +289,36 @@ export class QueryBuilder> extends BaseQuery { } if (this.#query?.query?.$case === "update") { - if (this.isScanCriteria(inputs[0])) { - for (const input of inputs as ScanCriteria[]) { - this.#query.query.update.where.push(this.buildForScanCriteria(input)); + for (const input of inputs) { + if (this.isScanCriteria(input)) { + for (const scan of inputs as ScanCriteria[]) { + this.#query.query.update.where.push( + this.buildForScanCriteria(scan) + ); + } + } else if (this.isConditionGroup(input)) { + this.#query.query.update.where.push( + this.buildForConditionGroup(input) + ); + } else { + this.#query.query.update.where.push(this.buildForCondition(input)); } - } else if (this.isConditionGroup(inputs)) { - this.#query.query.update.where.push( - this.buildForConditionGroup(inputs) - ); - } else { - this.#query.query.update.where.push( - this.buildForCondition(inputs as Condition) - ); } } else if (this.#query?.query?.$case === "delete") { - if (this.isScanCriteria(inputs[0])) { - for (const input of inputs as ScanCriteria[]) { - this.#query.query.delete.where.push(this.buildForScanCriteria(input)); + for (const input of inputs) { + if (this.isScanCriteria(input)) { + for (const scan of inputs as ScanCriteria[]) { + this.#query.query.delete.where.push( + this.buildForScanCriteria(scan) + ); + } + } else if (this.isConditionGroup(input)) { + this.#query.query.delete.where.push( + this.buildForConditionGroup(input) + ); + } else { + this.#query.query.delete.where.push(this.buildForCondition(input)); } - } else if (this.isConditionGroup(inputs)) { - this.#query.query.delete.where.push( - this.buildForConditionGroup(inputs) - ); - } else { - this.#query.query.delete.where.push( - this.buildForCondition(inputs as Condition) - ); } } @@ -324,13 +331,13 @@ export class QueryBuilder> extends BaseQuery { private buildForConditionGroup( conditionGroup: ConditionGroup ): Partial { - const [group, conditions] = conditionGroup; + const { group, conditions } = conditionGroup; const wheres: WhereCriteria[] = _.map(conditions, (condition) => { if (this.isConditionGroup(condition)) { return this.buildForConditionGroup(condition); } - const [column, operator, value] = condition as Condition; + const { column, operator, value } = condition as Condition; return { whereType: { $case: "condition", @@ -354,7 +361,7 @@ export class QueryBuilder> extends BaseQuery { } private buildForCondition(condition: Condition): Partial { - const [column, operator, value] = condition; + const { column, operator, value } = condition; return { whereType: { @@ -428,8 +435,8 @@ export class QueryBuilder> extends BaseQuery { return ( criteria != null && typeof criteria === "object" && - typeof (criteria as ConditionGroup)[0] === "string" && - typeof (criteria as ConditionGroup)[1] === "object" + typeof (criteria as ConditionGroup).group === "string" && + typeof (criteria as ConditionGroup).conditions === "object" ); } From c5b572fd586888f9bc3db1f5c4d75bc859f4a944 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Mon, 10 Jul 2023 17:30:03 +0300 Subject: [PATCH 08/12] feat: update WhereCondition --- .../src/query/QueryBuilder.ts | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/clients/client-relational/src/query/QueryBuilder.ts b/clients/client-relational/src/query/QueryBuilder.ts index beaa306..63554b7 100644 --- a/clients/client-relational/src/query/QueryBuilder.ts +++ b/clients/client-relational/src/query/QueryBuilder.ts @@ -23,10 +23,10 @@ interface Condition { interface ConditionGroup { group: "and" | "or"; - conditions: WhereCondition; + conditions: WhereCondition[]; } -export type WhereCondition = Condition[] | ScanCriteria[] | ConditionGroup[]; +export type WhereCondition = Condition | ScanCriteria | ConditionGroup; interface Build { build: () => Query; @@ -38,27 +38,27 @@ interface Limit extends Build { } interface Where extends Build { - andWhere: (...inputs: WhereCondition) => Where; + andWhere: (...inputs: WhereCondition[]) => Where; limit: (limit: number) => Limit; offset: (offset: number) => Build; } interface WhereForModify extends Build { - andWhere: (...inputs: WhereCondition) => WhereForModify; + andWhere: (...inputs: WhereCondition[]) => WhereForModify; } interface Select extends Build { - where: (...inputs: WhereCondition) => Where; + where: (...inputs: WhereCondition[]) => Where; limit: (limit: number) => Limit; offset: (offset: number) => Build; } interface Update extends Build { - where: (...inputs: WhereCondition) => WhereForModify; + where: (...inputs: WhereCondition[]) => WhereForModify; } interface Delete { - where: (...inputs: WhereCondition) => WhereForModify; + where: (...inputs: WhereCondition[]) => WhereForModify; } export function and(...conditions: Condition[]): ConditionGroup { @@ -253,7 +253,7 @@ export class QueryBuilder> extends BaseQuery { // private orderBy(column: TableColumn, direction: "asc" | "desc") {} - private where(...inputs: WhereCondition): Where { + private where(...inputs: WhereCondition[]): Where { if (this.#query?.query?.$case !== "select") { throw new Error("Where can only be used in select queries"); } @@ -264,9 +264,7 @@ export class QueryBuilder> extends BaseQuery { for (const input of inputs) { if (this.isScanCriteria(input)) { - for (const scan of inputs as ScanCriteria[]) { - this.#query.query.select.where.push(this.buildForScanCriteria(scan)); - } + this.#query.query.select.where.push(this.buildForScanCriteria(input)); } else if (this.isConditionGroup(input)) { this.#query.query.select.where.push(this.buildForConditionGroup(input)); } else { @@ -283,7 +281,7 @@ export class QueryBuilder> extends BaseQuery { }; } - private whereForModify(...inputs: WhereCondition): WhereForModify { + private whereForModify(...inputs: WhereCondition[]): WhereForModify { if (!inputs.length) { throw new Error("Where requires at least one argument"); } @@ -291,11 +289,7 @@ export class QueryBuilder> extends BaseQuery { if (this.#query?.query?.$case === "update") { for (const input of inputs) { if (this.isScanCriteria(input)) { - for (const scan of inputs as ScanCriteria[]) { - this.#query.query.update.where.push( - this.buildForScanCriteria(scan) - ); - } + this.#query.query.update.where.push(this.buildForScanCriteria(input)); } else if (this.isConditionGroup(input)) { this.#query.query.update.where.push( this.buildForConditionGroup(input) @@ -307,11 +301,7 @@ export class QueryBuilder> extends BaseQuery { } else if (this.#query?.query?.$case === "delete") { for (const input of inputs) { if (this.isScanCriteria(input)) { - for (const scan of inputs as ScanCriteria[]) { - this.#query.query.delete.where.push( - this.buildForScanCriteria(scan) - ); - } + this.#query.query.delete.where.push(this.buildForScanCriteria(input)); } else if (this.isConditionGroup(input)) { this.#query.query.delete.where.push( this.buildForConditionGroup(input) From d35bdbd6846ca8b8b0ed4c2e63388b6b6748f561 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Mon, 10 Jul 2023 22:23:08 +0300 Subject: [PATCH 09/12] feat: add support for classic where builder --- .../src/query/QueryBuilder.ts | 163 +++++++++++++----- 1 file changed, 117 insertions(+), 46 deletions(-) diff --git a/clients/client-relational/src/query/QueryBuilder.ts b/clients/client-relational/src/query/QueryBuilder.ts index 63554b7..07a9745 100644 --- a/clients/client-relational/src/query/QueryBuilder.ts +++ b/clients/client-relational/src/query/QueryBuilder.ts @@ -15,18 +15,23 @@ import { import { BaseQuery } from "./BaseQuery"; import _ from "lodash"; -interface Condition { - column: TableColumn; - operator: Operator; - value: RelationalValue; -} +export type ConditionTuple = [ + column: TableColumn, + operator: Operator, + value: RelationalValue +]; -interface ConditionGroup { - group: "and" | "or"; - conditions: WhereCondition[]; -} +export type ConditionGroup = [ + group: "and" | "or", + conditions: WhereGroupCondition[] +]; -export type WhereCondition = Condition | ScanCriteria | ConditionGroup; +export type WhereCondition = ConditionTuple | ScanCriteria[]; + +export type WhereGroupCondition = + | ConditionTuple + | ScanCriteria + | ConditionGroup; interface Build { build: () => Query; @@ -38,35 +43,40 @@ interface Limit extends Build { } interface Where extends Build { - andWhere: (...inputs: WhereCondition[]) => Where; + andWhere: (...inputs: WhereCondition) => Where; + andWhereGroup: (...inputs: WhereGroupCondition[]) => Where; limit: (limit: number) => Limit; offset: (offset: number) => Build; } interface WhereForModify extends Build { - andWhere: (...inputs: WhereCondition[]) => WhereForModify; + andWhere: (...inputs: WhereCondition) => WhereForModify; + andWhereGroup: (...inputs: WhereGroupCondition[]) => WhereForModify; } interface Select extends Build { - where: (...inputs: WhereCondition[]) => Where; + where: (...inputs: WhereCondition) => Where; + whereGroup: (...inputs: WhereGroupCondition[]) => Where; limit: (limit: number) => Limit; offset: (offset: number) => Build; } interface Update extends Build { - where: (...inputs: WhereCondition[]) => WhereForModify; + where: (...inputs: WhereCondition) => WhereForModify; + whereGroup: (...inputs: WhereGroupCondition[]) => WhereForModify; } interface Delete { - where: (...inputs: WhereCondition[]) => WhereForModify; + where: (...inputs: WhereCondition) => WhereForModify; + whereGroup: (...inputs: WhereGroupCondition[]) => WhereForModify; } -export function and(...conditions: Condition[]): ConditionGroup { - return { group: "and", conditions }; +export function and(...conditions: WhereGroupCondition[]): ConditionGroup { + return ["and", conditions]; } -export function or(...conditions: Condition[]): ConditionGroup { - return { group: "or", conditions }; +export function or(...conditions: WhereGroupCondition[]): ConditionGroup { + return ["or", conditions]; } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -97,6 +107,7 @@ export class QueryBuilder> extends BaseQuery { return { where: this.where.bind(this), + whereGroup: this.whereGroup.bind(this), // orderBy: this.orderBy.bind(this), limit: this.limit.bind(this), offset: this.offset.bind(this), @@ -230,6 +241,7 @@ export class QueryBuilder> extends BaseQuery { return { where: this.whereForModify.bind(this), + whereGroup: this.whereGroupForModify.bind(this), build: this.build.bind(this), }; } @@ -248,12 +260,40 @@ export class QueryBuilder> extends BaseQuery { return { where: this.whereForModify.bind(this), + whereGroup: this.whereGroupForModify.bind(this), }; } // private orderBy(column: TableColumn, direction: "asc" | "desc") {} - private where(...inputs: WhereCondition[]): Where { + private where(...inputs: WhereCondition): Where { + if (this.#query?.query?.$case !== "select") { + throw new Error("Where can only be used in select queries"); + } + + if (!inputs.length) { + throw new Error("Where requires at least one argument"); + } + + if (this.isScanCriteriaArray(inputs)) { + for (const input of inputs) { + this.#query.query.select.where.push(this.buildForScanCriteria(input)); + } + } else { + this.#query.query.select.where.push(this.buildForCondition(inputs)); + } + + return { + andWhere: this.where.bind(this), + andWhereGroup: this.whereGroup.bind(this), + // orderBy: this.orderBy.bind(this), + limit: this.limit.bind(this), + offset: this.offset.bind(this), + build: this.build.bind(this), + }; + } + + private whereGroup(...inputs: WhereGroupCondition[]): Where { if (this.#query?.query?.$case !== "select") { throw new Error("Where can only be used in select queries"); } @@ -274,6 +314,7 @@ export class QueryBuilder> extends BaseQuery { return { andWhere: this.where.bind(this), + andWhereGroup: this.whereGroup.bind(this), // orderBy: this.orderBy.bind(this), limit: this.limit.bind(this), offset: this.offset.bind(this), @@ -281,7 +322,39 @@ export class QueryBuilder> extends BaseQuery { }; } - private whereForModify(...inputs: WhereCondition[]): WhereForModify { + private whereForModify(...inputs: WhereCondition): WhereForModify { + if (!inputs.length) { + throw new Error("Where requires at least one argument"); + } + + if (this.#query?.query?.$case === "update") { + if (this.isScanCriteriaArray(inputs)) { + for (const input of inputs) { + this.#query.query.update.where.push(this.buildForScanCriteria(input)); + } + } else { + this.#query.query.update.where.push(this.buildForCondition(inputs)); + } + } else if (this.#query?.query?.$case === "delete") { + if (this.isScanCriteriaArray(inputs)) { + for (const input of inputs) { + this.#query.query.delete.where.push(this.buildForScanCriteria(input)); + } + } else { + this.#query.query.delete.where.push(this.buildForCondition(inputs)); + } + } + + return { + andWhere: this.whereForModify.bind(this), + andWhereGroup: this.whereGroup.bind(this), + build: this.build.bind(this), + }; + } + + private whereGroupForModify( + ...inputs: WhereGroupCondition[] + ): WhereForModify { if (!inputs.length) { throw new Error("Where requires at least one argument"); } @@ -314,6 +387,7 @@ export class QueryBuilder> extends BaseQuery { return { andWhere: this.whereForModify.bind(this), + andWhereGroup: this.whereGroup.bind(this), build: this.build.bind(this), }; } @@ -321,23 +395,16 @@ export class QueryBuilder> extends BaseQuery { private buildForConditionGroup( conditionGroup: ConditionGroup ): Partial { - const { group, conditions } = conditionGroup; + const [group, conditions] = conditionGroup; const wheres: WhereCriteria[] = _.map(conditions, (condition) => { if (this.isConditionGroup(condition)) { return this.buildForConditionGroup(condition); + } else if (this.isScanCriteria(condition)) { + return this.buildForScanCriteria(condition); + } else { + return this.buildForCondition(condition); } - const { column, operator, value } = condition as Condition; - return { - whereType: { - $case: "condition", - condition: { - key: column.name, - operator, - value, - }, - }, - }; }); const whereType: Partial = {}; @@ -350,8 +417,8 @@ export class QueryBuilder> extends BaseQuery { return whereType; } - private buildForCondition(condition: Condition): Partial { - const { column, operator, value } = condition; + private buildForCondition(condition: ConditionTuple): Partial { + const [column, operator, value] = condition; return { whereType: { @@ -412,21 +479,29 @@ export class QueryBuilder> extends BaseQuery { return this.#query; } + private isScanCriteriaArray(criteria: unknown): criteria is ScanCriteria[] { + return ( + criteria != null && + _.isArray(criteria) && + this.isScanCriteria(criteria[0]) + ); + } + private isScanCriteria(criteria: unknown): criteria is ScanCriteria { return ( criteria != null && - typeof criteria === "object" && - typeof (criteria as ScanCriteria).key === "string" && - typeof (criteria as ScanCriteria).operator !== "undefined" + _.isObject(criteria) && + _.has(criteria, "key") && + _.has(criteria, "operator") ); } private isConditionGroup(criteria: unknown): criteria is ConditionGroup { return ( - criteria != null && - typeof criteria === "object" && - typeof (criteria as ConditionGroup).group === "string" && - typeof (criteria as ConditionGroup).conditions === "object" + _.isArray(criteria) && + criteria.length === 2 && + criteria[0] in ["and", "or"] && + _.isArray(criteria[2]) ); } @@ -446,10 +521,6 @@ export class QueryBuilder> extends BaseQuery { value = Value.unwrap(value); } - if (this.isWKTValue(value)) { - value = Value.unwrap(value); - } - if (dataType == null) { throw new Error(`Unknown column ${key}`); } From a9f6b5e36b01b888ea76e135847bdf2550e17e3e Mon Sep 17 00:00:00 2001 From: eisbilir Date: Tue, 11 Jul 2023 17:41:37 +0300 Subject: [PATCH 10/12] feat: add join function and refactor code --- .../src/interfaces/TableColumns.ts | 7 +- .../relational/relational.ts | 70 +++--- .../client-relational/src/query/BaseQuery.ts | 3 +- .../src/query/QueryBuilder.ts | 216 +++++++----------- 4 files changed, 137 insertions(+), 159 deletions(-) diff --git a/clients/client-relational/src/interfaces/TableColumns.ts b/clients/client-relational/src/interfaces/TableColumns.ts index c4c5881..0404892 100644 --- a/clients/client-relational/src/interfaces/TableColumns.ts +++ b/clients/client-relational/src/interfaces/TableColumns.ts @@ -1,6 +1,9 @@ -import { ColumnType } from "../models/data-access-layer/relational/relational"; +import { + Column, + ColumnType, +} from "../models/data-access-layer/relational/relational"; -export type TableColumn = { +export type TableColumn = Partial & { name: string; type: ColumnType; }; diff --git a/clients/client-relational/src/models/data-access-layer/relational/relational.ts b/clients/client-relational/src/models/data-access-layer/relational/relational.ts index b21d6b3..5f2954b 100644 --- a/clients/client-relational/src/models/data-access-layer/relational/relational.ts +++ b/clients/client-relational/src/models/data-access-layer/relational/relational.ts @@ -261,14 +261,15 @@ export interface Value { } export interface Column { - tableName?: string | undefined; + schema?: string | undefined; + tableName: string; name: string; - type?: ColumnType | undefined; + type: ColumnType; } export interface Condition { operator: Operator; - key: string; + key?: Column; value?: Value; } @@ -686,7 +687,7 @@ export const Value = { }; function createBaseColumn(): Column { - return { tableName: undefined, name: "", type: undefined }; + return { schema: undefined, tableName: "", name: "", type: 0 }; } export const Column = { @@ -694,14 +695,17 @@ export const Column = { message: Column, writer: _m0.Writer = _m0.Writer.create() ): _m0.Writer { - if (message.tableName !== undefined) { - writer.uint32(10).string(message.tableName); + if (message.schema !== undefined) { + writer.uint32(10).string(message.schema); + } + if (message.tableName !== "") { + writer.uint32(18).string(message.tableName); } if (message.name !== "") { - writer.uint32(18).string(message.name); + writer.uint32(26).string(message.name); } - if (message.type !== undefined) { - writer.uint32(24).int32(message.type); + if (message.type !== 0) { + writer.uint32(32).int32(message.type); } return writer; }, @@ -719,17 +723,24 @@ export const Column = { break; } - message.tableName = reader.string(); + message.schema = reader.string(); continue; case 2: if (tag !== 18) { break; } - message.name = reader.string(); + message.tableName = reader.string(); continue; case 3: - if (tag !== 24) { + if (tag !== 26) { + break; + } + + message.name = reader.string(); + continue; + case 4: + if (tag !== 32) { break; } @@ -746,21 +757,19 @@ export const Column = { fromJSON(object: any): Column { return { - tableName: isSet(object.tableName) ? String(object.tableName) : undefined, + schema: isSet(object.schema) ? String(object.schema) : undefined, + tableName: isSet(object.tableName) ? String(object.tableName) : "", name: isSet(object.name) ? String(object.name) : "", - type: isSet(object.type) ? columnTypeFromJSON(object.type) : undefined, + type: isSet(object.type) ? columnTypeFromJSON(object.type) : 0, }; }, toJSON(message: Column): unknown { const obj: any = {}; + message.schema !== undefined && (obj.schema = message.schema); message.tableName !== undefined && (obj.tableName = message.tableName); message.name !== undefined && (obj.name = message.name); - message.type !== undefined && - (obj.type = - message.type !== undefined - ? columnTypeToJSON(message.type) - : undefined); + message.type !== undefined && (obj.type = columnTypeToJSON(message.type)); return obj; }, @@ -770,15 +779,16 @@ export const Column = { fromPartial, I>>(object: I): Column { const message = createBaseColumn(); - message.tableName = object.tableName ?? undefined; + message.schema = object.schema ?? undefined; + message.tableName = object.tableName ?? ""; message.name = object.name ?? ""; - message.type = object.type ?? undefined; + message.type = object.type ?? 0; return message; }, }; function createBaseCondition(): Condition { - return { operator: 0, key: "", value: undefined }; + return { operator: 0, key: undefined, value: undefined }; } export const Condition = { @@ -789,8 +799,8 @@ export const Condition = { if (message.operator !== 0) { writer.uint32(8).int32(message.operator); } - if (message.key !== "") { - writer.uint32(18).string(message.key); + if (message.key !== undefined) { + Column.encode(message.key, writer.uint32(18).fork()).ldelim(); } if (message.value !== undefined) { Value.encode(message.value, writer.uint32(26).fork()).ldelim(); @@ -818,7 +828,7 @@ export const Condition = { break; } - message.key = reader.string(); + message.key = Column.decode(reader, reader.uint32()); continue; case 3: if (tag !== 26) { @@ -839,7 +849,7 @@ export const Condition = { fromJSON(object: any): Condition { return { operator: isSet(object.operator) ? operatorFromJSON(object.operator) : 0, - key: isSet(object.key) ? String(object.key) : "", + key: isSet(object.key) ? Column.fromJSON(object.key) : undefined, value: isSet(object.value) ? Value.fromJSON(object.value) : undefined, }; }, @@ -848,7 +858,8 @@ export const Condition = { const obj: any = {}; message.operator !== undefined && (obj.operator = operatorToJSON(message.operator)); - message.key !== undefined && (obj.key = message.key); + message.key !== undefined && + (obj.key = message.key ? Column.toJSON(message.key) : undefined); message.value !== undefined && (obj.value = message.value ? Value.toJSON(message.value) : undefined); return obj; @@ -863,7 +874,10 @@ export const Condition = { ): Condition { const message = createBaseCondition(); message.operator = object.operator ?? 0; - message.key = object.key ?? ""; + message.key = + object.key !== undefined && object.key !== null + ? Column.fromPartial(object.key) + : undefined; message.value = object.value !== undefined && object.value !== null ? Value.fromPartial(object.value) diff --git a/clients/client-relational/src/query/BaseQuery.ts b/clients/client-relational/src/query/BaseQuery.ts index 00b50f3..e9f15a1 100644 --- a/clients/client-relational/src/query/BaseQuery.ts +++ b/clients/client-relational/src/query/BaseQuery.ts @@ -1,7 +1,8 @@ import { Schema } from "../interfaces/Schema"; +import { ValueTypes } from "./QueryBuilder"; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export abstract class BaseQuery> { +export abstract class BaseQuery> { protected schema: Schema; constructor(schema: Schema) { diff --git a/clients/client-relational/src/query/QueryBuilder.ts b/clients/client-relational/src/query/QueryBuilder.ts index 07a9745..644ab2c 100644 --- a/clients/client-relational/src/query/QueryBuilder.ts +++ b/clients/client-relational/src/query/QueryBuilder.ts @@ -1,37 +1,33 @@ -import { - ScanCriteria, - Value, - Operator as LibCommonOperator, -} from "@topcoder-framework/lib-common"; -import { TableColumn } from "../interfaces/TableColumns"; import { ColumnType, Operator, Query, Value as RelationalValue, WhereCriteria, + Column, + Join as JoinModel, + JoinType, } from "../models/data-access-layer/relational/relational"; import { BaseQuery } from "./BaseQuery"; import _ from "lodash"; -export type ConditionTuple = [ - column: TableColumn, - operator: Operator, - value: RelationalValue -]; +export type Join = Omit; export type ConditionGroup = [ group: "and" | "or", conditions: WhereGroupCondition[] ]; -export type WhereCondition = ConditionTuple | ScanCriteria[]; +export type WhereCondition = [ + column: Column, + operator: Operator, + value: ValueTypes +]; + +export type WhereGroupCondition = WhereCondition | ConditionGroup; -export type WhereGroupCondition = - | ConditionTuple - | ScanCriteria - | ConditionGroup; +export type ValueTypes = string | number | boolean | null; interface Build { build: () => Query; @@ -55,6 +51,10 @@ interface WhereForModify extends Build { } interface Select extends Build { + join: (join: Join) => Select; + leftJoin: (join: Join) => Select; + rightJoin: (join: Join) => Select; + fullJoin: (join: Join) => Select; where: (...inputs: WhereCondition) => Where; whereGroup: (...inputs: WhereGroupCondition[]) => Where; limit: (limit: number) => Limit; @@ -80,10 +80,12 @@ export function or(...conditions: WhereGroupCondition[]): ConditionGroup { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export class QueryBuilder> extends BaseQuery { +export class QueryBuilder< + T extends Record +> extends BaseQuery { #query: Query | null = null; - public select(...columns: TableColumn[]): Select { + public select(...columns: Column[]): Select { this.#query = { query: { $case: "select", @@ -91,7 +93,8 @@ export class QueryBuilder> extends BaseQuery { schema: this.schema.dbSchema, table: this.schema.tableName, column: columns.map((col) => ({ - tableName: this.schema.tableName, + schema: col.schema, + tableName: col.tableName, name: col.name, type: col.type, })), @@ -106,6 +109,10 @@ export class QueryBuilder> extends BaseQuery { }; return { + join: this.join.bind(this, JoinType.JOIN_TYPE_INNER), + leftJoin: this.join.bind(this, JoinType.JOIN_TYPE_LEFT), + rightJoin: this.join.bind(this, JoinType.JOIN_TYPE_RIGHT), + fullJoin: this.join.bind(this, JoinType.JOIN_TYPE_FULL), where: this.where.bind(this), whereGroup: this.whereGroup.bind(this), // orderBy: this.orderBy.bind(this), @@ -115,9 +122,7 @@ export class QueryBuilder> extends BaseQuery { }; } - public insert>( - input: CreateInput - ): Build { + public insert(input: Partial): Build { // TODO: This is a hack to get the create and modify audit columns, we need to either use the same "column name" or add a new property to the schema let createAuditColumnName = "create_date"; let updateAuditColumnName = "modify_date"; @@ -155,7 +160,10 @@ export class QueryBuilder> extends BaseQuery { ) .map(([key, value]) => ({ column: this.schema.columns[key]?.name ?? key, - value: this.toRelationalValue(key, value), + value: this.toRelationalValue( + this.schema.columns[key].type, + value as T[string] + ), })), idTable: this.schema.tableName, idColumn: this.schema.idColumn ?? undefined, @@ -193,9 +201,7 @@ export class QueryBuilder> extends BaseQuery { }; } - public update>( - input: UpdateInput - ): Update { + public update(input: Partial): Update { let updateAuditColumnName = "modify_date"; let auditColumnKey = "modifyDate"; @@ -225,14 +231,19 @@ export class QueryBuilder> extends BaseQuery { }, }, }, - ...Object.entries(input) - .filter( - ([key, value]) => value !== undefined && key !== auditColumnKey - ) - .map(([key, value]) => ({ + ..._.map( + _.filter( + _.entries(input), + ([key, value]) => value !== undefined && key != auditColumnKey + ), + ([key, value]) => ({ column: this.schema.columns[key]?.name ?? key, - value: this.toRelationalValue(key, value), - })), + value: this.toRelationalValue( + this.schema.columns[key].type, + value as T[string] + ), + }) + ), ], where: [], }, @@ -264,6 +275,25 @@ export class QueryBuilder> extends BaseQuery { }; } + private join(type: JoinType, join: Join): Select { + if (this.#query?.query?.$case !== "select") { + throw new Error("Where can only be used in select queries"); + } + this.#query?.query.select.join.push(_.assign({}, join, { type })); + return { + join: this.join.bind(this, JoinType.JOIN_TYPE_INNER), + leftJoin: this.join.bind(this, JoinType.JOIN_TYPE_LEFT), + rightJoin: this.join.bind(this, JoinType.JOIN_TYPE_RIGHT), + fullJoin: this.join.bind(this, JoinType.JOIN_TYPE_FULL), + where: this.where.bind(this), + whereGroup: this.whereGroup.bind(this), + // orderBy: this.orderBy.bind(this), + limit: this.limit.bind(this), + offset: this.offset.bind(this), + build: this.build.bind(this), + }; + } + // private orderBy(column: TableColumn, direction: "asc" | "desc") {} private where(...inputs: WhereCondition): Where { @@ -275,13 +305,7 @@ export class QueryBuilder> extends BaseQuery { throw new Error("Where requires at least one argument"); } - if (this.isScanCriteriaArray(inputs)) { - for (const input of inputs) { - this.#query.query.select.where.push(this.buildForScanCriteria(input)); - } - } else { - this.#query.query.select.where.push(this.buildForCondition(inputs)); - } + this.#query.query.select.where.push(this.buildForCondition(inputs)); return { andWhere: this.where.bind(this), @@ -303,9 +327,7 @@ export class QueryBuilder> extends BaseQuery { } for (const input of inputs) { - if (this.isScanCriteria(input)) { - this.#query.query.select.where.push(this.buildForScanCriteria(input)); - } else if (this.isConditionGroup(input)) { + if (this.isConditionGroup(input)) { this.#query.query.select.where.push(this.buildForConditionGroup(input)); } else { this.#query.query.select.where.push(this.buildForCondition(input)); @@ -328,21 +350,9 @@ export class QueryBuilder> extends BaseQuery { } if (this.#query?.query?.$case === "update") { - if (this.isScanCriteriaArray(inputs)) { - for (const input of inputs) { - this.#query.query.update.where.push(this.buildForScanCriteria(input)); - } - } else { - this.#query.query.update.where.push(this.buildForCondition(inputs)); - } + this.#query.query.update.where.push(this.buildForCondition(inputs)); } else if (this.#query?.query?.$case === "delete") { - if (this.isScanCriteriaArray(inputs)) { - for (const input of inputs) { - this.#query.query.delete.where.push(this.buildForScanCriteria(input)); - } - } else { - this.#query.query.delete.where.push(this.buildForCondition(inputs)); - } + this.#query.query.delete.where.push(this.buildForCondition(inputs)); } return { @@ -361,9 +371,7 @@ export class QueryBuilder> extends BaseQuery { if (this.#query?.query?.$case === "update") { for (const input of inputs) { - if (this.isScanCriteria(input)) { - this.#query.query.update.where.push(this.buildForScanCriteria(input)); - } else if (this.isConditionGroup(input)) { + if (this.isConditionGroup(input)) { this.#query.query.update.where.push( this.buildForConditionGroup(input) ); @@ -373,9 +381,7 @@ export class QueryBuilder> extends BaseQuery { } } else if (this.#query?.query?.$case === "delete") { for (const input of inputs) { - if (this.isScanCriteria(input)) { - this.#query.query.delete.where.push(this.buildForScanCriteria(input)); - } else if (this.isConditionGroup(input)) { + if (this.isConditionGroup(input)) { this.#query.query.delete.where.push( this.buildForConditionGroup(input) ); @@ -400,8 +406,6 @@ export class QueryBuilder> extends BaseQuery { const wheres: WhereCriteria[] = _.map(conditions, (condition) => { if (this.isConditionGroup(condition)) { return this.buildForConditionGroup(condition); - } else if (this.isScanCriteria(condition)) { - return this.buildForScanCriteria(condition); } else { return this.buildForCondition(condition); } @@ -417,32 +421,16 @@ export class QueryBuilder> extends BaseQuery { return whereType; } - private buildForCondition(condition: ConditionTuple): Partial { + private buildForCondition(condition: WhereCondition): Partial { const [column, operator, value] = condition; return { whereType: { $case: "condition", condition: { - key: column.name, + key: column, operator, - value, - }, - }, - }; - } - - private buildForScanCriteria(criteria: ScanCriteria): Partial { - return { - whereType: { - $case: "condition", - condition: { - key: this.schema.columns[criteria.key].name, - operator: - criteria.operator === LibCommonOperator.OPERATOR_NOT_EQUAL - ? Operator.OPERATOR_NOT_EQUAL - : Operator.OPERATOR_EQUAL, - value: this.toRelationalValue(criteria.key, criteria.value), + value: this.toRelationalValue(column.type, value), }, }, }; @@ -479,23 +467,6 @@ export class QueryBuilder> extends BaseQuery { return this.#query; } - private isScanCriteriaArray(criteria: unknown): criteria is ScanCriteria[] { - return ( - criteria != null && - _.isArray(criteria) && - this.isScanCriteria(criteria[0]) - ); - } - - private isScanCriteria(criteria: unknown): criteria is ScanCriteria { - return ( - criteria != null && - _.isObject(criteria) && - _.has(criteria, "key") && - _.has(criteria, "operator") - ); - } - private isConditionGroup(criteria: unknown): criteria is ConditionGroup { return ( _.isArray(criteria) && @@ -505,62 +476,51 @@ export class QueryBuilder> extends BaseQuery { ); } - private isWKTValue(value: unknown): value is Value { - return ( - typeof value === "object" && - value != null && - typeof (value as Value)?.kind != undefined && - typeof (value as Value)?.kind?.$case === "string" - ); - } - - private toRelationalValue(key: string, value: unknown): RelationalValue { - const dataType: ColumnType = this.schema.columns[key].type; - - if (this.isWKTValue(value)) { - value = Value.unwrap(value); - } - - if (dataType == null) { - throw new Error(`Unknown column ${key}`); + private toRelationalValue( + columnType: ColumnType, + value: ValueTypes + ): RelationalValue | undefined { + // is null or set null + if (value === null) { + return undefined; } - if (dataType === ColumnType.COLUMN_TYPE_INT) { + if (columnType === ColumnType.COLUMN_TYPE_INT) { return { value: { $case: "intValue", intValue: value as number } }; } - if (dataType === ColumnType.COLUMN_TYPE_LONG) { + if (columnType === ColumnType.COLUMN_TYPE_LONG) { return { value: { $case: "longValue", longValue: value as number } }; } - if (dataType === ColumnType.COLUMN_TYPE_FLOAT) { + if (columnType === ColumnType.COLUMN_TYPE_FLOAT) { return { value: { $case: "floatValue", floatValue: value as number } }; } - if (dataType === ColumnType.COLUMN_TYPE_DOUBLE) { + if (columnType === ColumnType.COLUMN_TYPE_DOUBLE) { return { value: { $case: "doubleValue", doubleValue: value as number } }; } - if (dataType === ColumnType.COLUMN_TYPE_DATE) { + if (columnType === ColumnType.COLUMN_TYPE_DATE) { return { value: { $case: "dateValue", dateValue: value as string } }; } - if (dataType == ColumnType.COLUMN_TYPE_DATETIME) { + if (columnType == ColumnType.COLUMN_TYPE_DATETIME) { return { value: { $case: "datetimeValue", datetimeValue: value as string }, }; } - if (dataType == ColumnType.COLUMN_TYPE_STRING) { + if (columnType == ColumnType.COLUMN_TYPE_STRING) { return { value: { $case: "stringValue", stringValue: value as string } }; } - if (dataType == ColumnType.COLUMN_TYPE_BOOLEAN) { + if (columnType == ColumnType.COLUMN_TYPE_BOOLEAN) { return { value: { $case: "booleanValue", booleanValue: value as boolean }, }; } - throw new Error(`Unsupported data type ${dataType}`); + throw new Error(`Unsupported data type ${columnType}`); } } From 3d536da0de9f4ad5dd37f31f25ebf873afe0488f Mon Sep 17 00:00:00 2001 From: eisbilir Date: Wed, 12 Jul 2023 18:05:45 +0300 Subject: [PATCH 11/12] feat: add model --- .../src/common/ModelBuilder.ts | 22 + clients/client-relational/src/common/index.ts | 1 + clients/client-relational/src/index.ts | 1 + .../client-relational/src/interfaces/Model.ts | 7 + .../src/interfaces/Schema.ts | 16 +- .../src/interfaces/TableColumns.ts | 12 +- .../client-relational/src/interfaces/index.ts | 1 + .../relational/relational.ts | 679 ++++++++++++++---- .../client-relational/src/query/BaseQuery.ts | 12 +- .../src/query/QueryBuilder.ts | 181 +++-- 10 files changed, 719 insertions(+), 213 deletions(-) create mode 100644 clients/client-relational/src/common/ModelBuilder.ts create mode 100644 clients/client-relational/src/common/index.ts create mode 100644 clients/client-relational/src/interfaces/Model.ts diff --git a/clients/client-relational/src/common/ModelBuilder.ts b/clients/client-relational/src/common/ModelBuilder.ts new file mode 100644 index 0000000..d1d18ac --- /dev/null +++ b/clients/client-relational/src/common/ModelBuilder.ts @@ -0,0 +1,22 @@ +import { Schema } from "../interfaces/Schema"; +import _ from "lodash"; +import { Model } from "src/interfaces/Model"; + +export function constructModel>( + schema: Schema +): Model { + const model: Model = { + schema: schema.dbSchema, + tableName: schema.tableName, + columns: _.mapValues(schema.columns, (col, key) => { + return { + schema: schema.dbSchema, + tableName: schema.tableName, + name: col.name, + alias: key, + type: col.type, + }; + }), + }; + return model; +} diff --git a/clients/client-relational/src/common/index.ts b/clients/client-relational/src/common/index.ts new file mode 100644 index 0000000..2e40db4 --- /dev/null +++ b/clients/client-relational/src/common/index.ts @@ -0,0 +1 @@ +export * from "./ModelBuilder"; diff --git a/clients/client-relational/src/index.ts b/clients/client-relational/src/index.ts index 5856907..dc6a5b8 100644 --- a/clients/client-relational/src/index.ts +++ b/clients/client-relational/src/index.ts @@ -2,3 +2,4 @@ export * from "./client/RelationalClient"; export * from "./interfaces/"; export * from "./models/data-access-layer/relational/relational"; export * from "./query/"; +export * from "./common"; diff --git a/clients/client-relational/src/interfaces/Model.ts b/clients/client-relational/src/interfaces/Model.ts new file mode 100644 index 0000000..2ae91a0 --- /dev/null +++ b/clients/client-relational/src/interfaces/Model.ts @@ -0,0 +1,7 @@ +import { TableColumns } from "./TableColumns"; + +export type Model> = { + schema: string; + tableName: string; + columns: TableColumns; +}; diff --git a/clients/client-relational/src/interfaces/Schema.ts b/clients/client-relational/src/interfaces/Schema.ts index ade6533..14a5b77 100644 --- a/clients/client-relational/src/interfaces/Schema.ts +++ b/clients/client-relational/src/interfaces/Schema.ts @@ -1,6 +1,8 @@ -import { TableColumns } from "./TableColumns"; +import { + ColumnType, + TypedColumn, +} from "../models/data-access-layer/relational/relational"; -// eslint-disable-next-line @typescript-eslint/no-explicit-any export type Schema> = { dbSchema: string; tableName: string; @@ -8,5 +10,13 @@ export type Schema> = { idSequence?: string; idTable?: string; columns: TableColumns; - returningFields?: string[]; +}; + +type TableColumn = Partial & { + name: string; + type: ColumnType; +}; + +type TableColumns> = { + [Property in keyof T]: TableColumn; }; diff --git a/clients/client-relational/src/interfaces/TableColumns.ts b/clients/client-relational/src/interfaces/TableColumns.ts index 0404892..fc304a2 100644 --- a/clients/client-relational/src/interfaces/TableColumns.ts +++ b/clients/client-relational/src/interfaces/TableColumns.ts @@ -1,13 +1,5 @@ -import { - Column, - ColumnType, -} from "../models/data-access-layer/relational/relational"; - -export type TableColumn = Partial & { - name: string; - type: ColumnType; -}; +import { TypedColumn } from "../models/data-access-layer/relational/relational"; export type TableColumns> = { - [Property in keyof T]: TableColumn; + [Property in keyof T]: TypedColumn; }; diff --git a/clients/client-relational/src/interfaces/index.ts b/clients/client-relational/src/interfaces/index.ts index ebd5d5d..767b302 100644 --- a/clients/client-relational/src/interfaces/index.ts +++ b/clients/client-relational/src/interfaces/index.ts @@ -1,2 +1,3 @@ export * from "./Schema"; export * from "./TableColumns"; +export * from "./Model"; diff --git a/clients/client-relational/src/models/data-access-layer/relational/relational.ts b/clients/client-relational/src/models/data-access-layer/relational/relational.ts index 5f2954b..0826632 100644 --- a/clients/client-relational/src/models/data-access-layer/relational/relational.ts +++ b/clients/client-relational/src/models/data-access-layer/relational/relational.ts @@ -260,17 +260,30 @@ export interface Value { | { $case: "blobValue"; blobValue: Buffer }; } +export interface TypedColumn { + schema: string; + tableName: string; + name: string; + alias?: string | undefined; + type: ColumnType; +} + export interface Column { - schema?: string | undefined; + schema: string; tableName: string; name: string; +} + +export interface ReturningColumn { + name: string; + alias?: string | undefined; type: ColumnType; } export interface Condition { operator: Operator; key?: Column; - value?: Value; + value: Value[]; } export interface AndWhere { @@ -295,23 +308,32 @@ export interface RawQuery { query: string; } +export interface Table { + schema: string; + tableName: string; +} + +export interface JoinCondition { + operator: Operator; + left?: Column; + right?: + | { $case: "value"; value: Value } + | { $case: "column"; column: Column }; +} + export interface Join { type: JoinType; - fromTableSchema?: string | undefined; - joinTableSchema?: string | undefined; - fromTable: string; - joinTable: string; - fromColumn: string; - joinColumn: string; + table?: Table; + conditions: JoinCondition[]; } export interface SelectQuery { - schema?: string | undefined; + schema: string; table: string; - column: Column[]; + column: TypedColumn[]; where: WhereCriteria[]; - groupBy: string[]; - orderBy: string[]; + groupBy: Column[]; + orderBy: Column[]; join: Join[]; limit: number; offset: number; @@ -323,13 +345,13 @@ export interface ColumnValue { } export interface InsertQuery { - schema?: string | undefined; + schema: string; table: string; columnValue: ColumnValue[]; idColumn?: string | undefined; idSequence?: string | undefined; idTable?: string | undefined; - returningFields: string[]; + returningFields: ReturningColumn[]; } export interface BulkInsertQuery { @@ -337,14 +359,14 @@ export interface BulkInsertQuery { } export interface UpdateQuery { - schema?: string | undefined; + schema: string; table: string; columnValue: ColumnValue[]; where: WhereCriteria[]; } export interface DeleteQuery { - schema?: string | undefined; + schema: string; table: string; where: WhereCriteria[]; } @@ -686,16 +708,16 @@ export const Value = { }, }; -function createBaseColumn(): Column { - return { schema: undefined, tableName: "", name: "", type: 0 }; +function createBaseTypedColumn(): TypedColumn { + return { schema: "", tableName: "", name: "", alias: undefined, type: 0 }; } -export const Column = { +export const TypedColumn = { encode( - message: Column, + message: TypedColumn, writer: _m0.Writer = _m0.Writer.create() ): _m0.Writer { - if (message.schema !== undefined) { + if (message.schema !== "") { writer.uint32(10).string(message.schema); } if (message.tableName !== "") { @@ -704,17 +726,20 @@ export const Column = { if (message.name !== "") { writer.uint32(26).string(message.name); } + if (message.alias !== undefined) { + writer.uint32(34).string(message.alias); + } if (message.type !== 0) { - writer.uint32(32).int32(message.type); + writer.uint32(40).int32(message.type); } return writer; }, - decode(input: _m0.Reader | Uint8Array, length?: number): Column { + decode(input: _m0.Reader | Uint8Array, length?: number): TypedColumn { const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseColumn(); + const message = createBaseTypedColumn(); while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { @@ -740,7 +765,14 @@ export const Column = { message.name = reader.string(); continue; case 4: - if (tag !== 32) { + if (tag !== 34) { + break; + } + + message.alias = reader.string(); + continue; + case 5: + if (tag !== 40) { break; } @@ -755,40 +787,225 @@ export const Column = { return message; }, - fromJSON(object: any): Column { + fromJSON(object: any): TypedColumn { return { - schema: isSet(object.schema) ? String(object.schema) : undefined, + schema: isSet(object.schema) ? String(object.schema) : "", tableName: isSet(object.tableName) ? String(object.tableName) : "", name: isSet(object.name) ? String(object.name) : "", + alias: isSet(object.alias) ? String(object.alias) : undefined, type: isSet(object.type) ? columnTypeFromJSON(object.type) : 0, }; }, - toJSON(message: Column): unknown { + toJSON(message: TypedColumn): unknown { const obj: any = {}; message.schema !== undefined && (obj.schema = message.schema); message.tableName !== undefined && (obj.tableName = message.tableName); message.name !== undefined && (obj.name = message.name); + message.alias !== undefined && (obj.alias = message.alias); message.type !== undefined && (obj.type = columnTypeToJSON(message.type)); return obj; }, + create, I>>(base?: I): TypedColumn { + return TypedColumn.fromPartial(base ?? {}); + }, + + fromPartial, I>>( + object: I + ): TypedColumn { + const message = createBaseTypedColumn(); + message.schema = object.schema ?? ""; + message.tableName = object.tableName ?? ""; + message.name = object.name ?? ""; + message.alias = object.alias ?? undefined; + message.type = object.type ?? 0; + return message; + }, +}; + +function createBaseColumn(): Column { + return { schema: "", tableName: "", name: "" }; +} + +export const Column = { + encode( + message: Column, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.schema !== "") { + writer.uint32(10).string(message.schema); + } + if (message.tableName !== "") { + writer.uint32(18).string(message.tableName); + } + if (message.name !== "") { + writer.uint32(26).string(message.name); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Column { + const reader = + input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseColumn(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.schema = reader.string(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.tableName = reader.string(); + continue; + case 3: + if (tag !== 26) { + break; + } + + message.name = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Column { + return { + schema: isSet(object.schema) ? String(object.schema) : "", + tableName: isSet(object.tableName) ? String(object.tableName) : "", + name: isSet(object.name) ? String(object.name) : "", + }; + }, + + toJSON(message: Column): unknown { + const obj: any = {}; + message.schema !== undefined && (obj.schema = message.schema); + message.tableName !== undefined && (obj.tableName = message.tableName); + message.name !== undefined && (obj.name = message.name); + return obj; + }, + create, I>>(base?: I): Column { return Column.fromPartial(base ?? {}); }, fromPartial, I>>(object: I): Column { const message = createBaseColumn(); - message.schema = object.schema ?? undefined; + message.schema = object.schema ?? ""; message.tableName = object.tableName ?? ""; message.name = object.name ?? ""; + return message; + }, +}; + +function createBaseReturningColumn(): ReturningColumn { + return { name: "", alias: undefined, type: 0 }; +} + +export const ReturningColumn = { + encode( + message: ReturningColumn, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.name !== "") { + writer.uint32(10).string(message.name); + } + if (message.alias !== undefined) { + writer.uint32(18).string(message.alias); + } + if (message.type !== 0) { + writer.uint32(24).int32(message.type); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): ReturningColumn { + const reader = + input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseReturningColumn(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.name = reader.string(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.alias = reader.string(); + continue; + case 3: + if (tag !== 24) { + break; + } + + message.type = reader.int32() as any; + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): ReturningColumn { + return { + name: isSet(object.name) ? String(object.name) : "", + alias: isSet(object.alias) ? String(object.alias) : undefined, + type: isSet(object.type) ? columnTypeFromJSON(object.type) : 0, + }; + }, + + toJSON(message: ReturningColumn): unknown { + const obj: any = {}; + message.name !== undefined && (obj.name = message.name); + message.alias !== undefined && (obj.alias = message.alias); + message.type !== undefined && (obj.type = columnTypeToJSON(message.type)); + return obj; + }, + + create, I>>( + base?: I + ): ReturningColumn { + return ReturningColumn.fromPartial(base ?? {}); + }, + + fromPartial, I>>( + object: I + ): ReturningColumn { + const message = createBaseReturningColumn(); + message.name = object.name ?? ""; + message.alias = object.alias ?? undefined; message.type = object.type ?? 0; return message; }, }; function createBaseCondition(): Condition { - return { operator: 0, key: undefined, value: undefined }; + return { operator: 0, key: undefined, value: [] }; } export const Condition = { @@ -802,8 +1019,8 @@ export const Condition = { if (message.key !== undefined) { Column.encode(message.key, writer.uint32(18).fork()).ldelim(); } - if (message.value !== undefined) { - Value.encode(message.value, writer.uint32(26).fork()).ldelim(); + for (const v of message.value) { + Value.encode(v!, writer.uint32(26).fork()).ldelim(); } return writer; }, @@ -835,7 +1052,7 @@ export const Condition = { break; } - message.value = Value.decode(reader, reader.uint32()); + message.value.push(Value.decode(reader, reader.uint32())); continue; } if ((tag & 7) === 4 || tag === 0) { @@ -850,7 +1067,9 @@ export const Condition = { return { operator: isSet(object.operator) ? operatorFromJSON(object.operator) : 0, key: isSet(object.key) ? Column.fromJSON(object.key) : undefined, - value: isSet(object.value) ? Value.fromJSON(object.value) : undefined, + value: Array.isArray(object?.value) + ? object.value.map((e: any) => Value.fromJSON(e)) + : [], }; }, @@ -860,8 +1079,11 @@ export const Condition = { (obj.operator = operatorToJSON(message.operator)); message.key !== undefined && (obj.key = message.key ? Column.toJSON(message.key) : undefined); - message.value !== undefined && - (obj.value = message.value ? Value.toJSON(message.value) : undefined); + if (message.value) { + obj.value = message.value.map((e) => (e ? Value.toJSON(e) : undefined)); + } else { + obj.value = []; + } return obj; }, @@ -878,10 +1100,7 @@ export const Condition = { object.key !== undefined && object.key !== null ? Column.fromPartial(object.key) : undefined; - message.value = - object.value !== undefined && object.value !== null - ? Value.fromPartial(object.value) - : undefined; + message.value = object.value?.map((e) => Value.fromPartial(e)) || []; return message; }, }; @@ -1240,49 +1459,109 @@ export const RawQuery = { }, }; -function createBaseJoin(): Join { - return { - type: 0, - fromTableSchema: undefined, - joinTableSchema: undefined, - fromTable: "", - joinTable: "", - fromColumn: "", - joinColumn: "", - }; +function createBaseTable(): Table { + return { schema: "", tableName: "" }; } -export const Join = { - encode(message: Join, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { - if (message.type !== 0) { - writer.uint32(8).int32(message.type); - } - if (message.fromTableSchema !== undefined) { - writer.uint32(18).string(message.fromTableSchema); +export const Table = { + encode(message: Table, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.schema !== "") { + writer.uint32(10).string(message.schema); } - if (message.joinTableSchema !== undefined) { - writer.uint32(26).string(message.joinTableSchema); + if (message.tableName !== "") { + writer.uint32(18).string(message.tableName); } - if (message.fromTable !== "") { - writer.uint32(34).string(message.fromTable); + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Table { + const reader = + input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTable(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.schema = reader.string(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.tableName = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); } - if (message.joinTable !== "") { - writer.uint32(42).string(message.joinTable); + return message; + }, + + fromJSON(object: any): Table { + return { + schema: isSet(object.schema) ? String(object.schema) : "", + tableName: isSet(object.tableName) ? String(object.tableName) : "", + }; + }, + + toJSON(message: Table): unknown { + const obj: any = {}; + message.schema !== undefined && (obj.schema = message.schema); + message.tableName !== undefined && (obj.tableName = message.tableName); + return obj; + }, + + create, I>>(base?: I): Table { + return Table.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): Table { + const message = createBaseTable(); + message.schema = object.schema ?? ""; + message.tableName = object.tableName ?? ""; + return message; + }, +}; + +function createBaseJoinCondition(): JoinCondition { + return { operator: 0, left: undefined, right: undefined }; +} + +export const JoinCondition = { + encode( + message: JoinCondition, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.operator !== 0) { + writer.uint32(8).int32(message.operator); } - if (message.fromColumn !== "") { - writer.uint32(50).string(message.fromColumn); + if (message.left !== undefined) { + Column.encode(message.left, writer.uint32(18).fork()).ldelim(); } - if (message.joinColumn !== "") { - writer.uint32(58).string(message.joinColumn); + switch (message.right?.$case) { + case "value": + Value.encode(message.right.value, writer.uint32(26).fork()).ldelim(); + break; + case "column": + Column.encode(message.right.column, writer.uint32(34).fork()).ldelim(); + break; } return writer; }, - decode(input: _m0.Reader | Uint8Array, length?: number): Join { + decode(input: _m0.Reader | Uint8Array, length?: number): JoinCondition { const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseJoin(); + const message = createBaseJoinCondition(); while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { @@ -1291,49 +1570,160 @@ export const Join = { break; } - message.type = reader.int32() as any; + message.operator = reader.int32() as any; continue; case 2: if (tag !== 18) { break; } - message.fromTableSchema = reader.string(); + message.left = Column.decode(reader, reader.uint32()); continue; case 3: if (tag !== 26) { break; } - message.joinTableSchema = reader.string(); + message.right = { + $case: "value", + value: Value.decode(reader, reader.uint32()), + }; continue; case 4: if (tag !== 34) { break; } - message.fromTable = reader.string(); + message.right = { + $case: "column", + column: Column.decode(reader, reader.uint32()), + }; continue; - case 5: - if (tag !== 42) { + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): JoinCondition { + return { + operator: isSet(object.operator) ? operatorFromJSON(object.operator) : 0, + left: isSet(object.left) ? Column.fromJSON(object.left) : undefined, + right: isSet(object.value) + ? { $case: "value", value: Value.fromJSON(object.value) } + : isSet(object.column) + ? { $case: "column", column: Column.fromJSON(object.column) } + : undefined, + }; + }, + + toJSON(message: JoinCondition): unknown { + const obj: any = {}; + message.operator !== undefined && + (obj.operator = operatorToJSON(message.operator)); + message.left !== undefined && + (obj.left = message.left ? Column.toJSON(message.left) : undefined); + message.right?.$case === "value" && + (obj.value = message.right?.value + ? Value.toJSON(message.right?.value) + : undefined); + message.right?.$case === "column" && + (obj.column = message.right?.column + ? Column.toJSON(message.right?.column) + : undefined); + return obj; + }, + + create, I>>( + base?: I + ): JoinCondition { + return JoinCondition.fromPartial(base ?? {}); + }, + + fromPartial, I>>( + object: I + ): JoinCondition { + const message = createBaseJoinCondition(); + message.operator = object.operator ?? 0; + message.left = + object.left !== undefined && object.left !== null + ? Column.fromPartial(object.left) + : undefined; + if ( + object.right?.$case === "value" && + object.right?.value !== undefined && + object.right?.value !== null + ) { + message.right = { + $case: "value", + value: Value.fromPartial(object.right.value), + }; + } + if ( + object.right?.$case === "column" && + object.right?.column !== undefined && + object.right?.column !== null + ) { + message.right = { + $case: "column", + column: Column.fromPartial(object.right.column), + }; + } + return message; + }, +}; + +function createBaseJoin(): Join { + return { type: 0, table: undefined, conditions: [] }; +} + +export const Join = { + encode(message: Join, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.type !== 0) { + writer.uint32(8).int32(message.type); + } + if (message.table !== undefined) { + Table.encode(message.table, writer.uint32(18).fork()).ldelim(); + } + for (const v of message.conditions) { + JoinCondition.encode(v!, writer.uint32(26).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Join { + const reader = + input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseJoin(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { break; } - message.joinTable = reader.string(); + message.type = reader.int32() as any; continue; - case 6: - if (tag !== 50) { + case 2: + if (tag !== 18) { break; } - message.fromColumn = reader.string(); + message.table = Table.decode(reader, reader.uint32()); continue; - case 7: - if (tag !== 58) { + case 3: + if (tag !== 26) { break; } - message.joinColumn = reader.string(); + message.conditions.push( + JoinCondition.decode(reader, reader.uint32()) + ); continue; } if ((tag & 7) === 4 || tag === 0) { @@ -1347,30 +1737,25 @@ export const Join = { fromJSON(object: any): Join { return { type: isSet(object.type) ? joinTypeFromJSON(object.type) : 0, - fromTableSchema: isSet(object.fromTableSchema) - ? String(object.fromTableSchema) - : undefined, - joinTableSchema: isSet(object.joinTableSchema) - ? String(object.joinTableSchema) - : undefined, - fromTable: isSet(object.fromTable) ? String(object.fromTable) : "", - joinTable: isSet(object.joinTable) ? String(object.joinTable) : "", - fromColumn: isSet(object.fromColumn) ? String(object.fromColumn) : "", - joinColumn: isSet(object.joinColumn) ? String(object.joinColumn) : "", + table: isSet(object.table) ? Table.fromJSON(object.table) : undefined, + conditions: Array.isArray(object?.conditions) + ? object.conditions.map((e: any) => JoinCondition.fromJSON(e)) + : [], }; }, toJSON(message: Join): unknown { const obj: any = {}; message.type !== undefined && (obj.type = joinTypeToJSON(message.type)); - message.fromTableSchema !== undefined && - (obj.fromTableSchema = message.fromTableSchema); - message.joinTableSchema !== undefined && - (obj.joinTableSchema = message.joinTableSchema); - message.fromTable !== undefined && (obj.fromTable = message.fromTable); - message.joinTable !== undefined && (obj.joinTable = message.joinTable); - message.fromColumn !== undefined && (obj.fromColumn = message.fromColumn); - message.joinColumn !== undefined && (obj.joinColumn = message.joinColumn); + message.table !== undefined && + (obj.table = message.table ? Table.toJSON(message.table) : undefined); + if (message.conditions) { + obj.conditions = message.conditions.map((e) => + e ? JoinCondition.toJSON(e) : undefined + ); + } else { + obj.conditions = []; + } return obj; }, @@ -1381,19 +1766,19 @@ export const Join = { fromPartial, I>>(object: I): Join { const message = createBaseJoin(); message.type = object.type ?? 0; - message.fromTableSchema = object.fromTableSchema ?? undefined; - message.joinTableSchema = object.joinTableSchema ?? undefined; - message.fromTable = object.fromTable ?? ""; - message.joinTable = object.joinTable ?? ""; - message.fromColumn = object.fromColumn ?? ""; - message.joinColumn = object.joinColumn ?? ""; + message.table = + object.table !== undefined && object.table !== null + ? Table.fromPartial(object.table) + : undefined; + message.conditions = + object.conditions?.map((e) => JoinCondition.fromPartial(e)) || []; return message; }, }; function createBaseSelectQuery(): SelectQuery { return { - schema: undefined, + schema: "", table: "", column: [], where: [], @@ -1410,23 +1795,23 @@ export const SelectQuery = { message: SelectQuery, writer: _m0.Writer = _m0.Writer.create() ): _m0.Writer { - if (message.schema !== undefined) { + if (message.schema !== "") { writer.uint32(10).string(message.schema); } if (message.table !== "") { writer.uint32(18).string(message.table); } for (const v of message.column) { - Column.encode(v!, writer.uint32(26).fork()).ldelim(); + TypedColumn.encode(v!, writer.uint32(26).fork()).ldelim(); } for (const v of message.where) { WhereCriteria.encode(v!, writer.uint32(34).fork()).ldelim(); } for (const v of message.groupBy) { - writer.uint32(42).string(v!); + Column.encode(v!, writer.uint32(42).fork()).ldelim(); } for (const v of message.orderBy) { - writer.uint32(50).string(v!); + Column.encode(v!, writer.uint32(50).fork()).ldelim(); } for (const v of message.join) { Join.encode(v!, writer.uint32(58).fork()).ldelim(); @@ -1467,7 +1852,7 @@ export const SelectQuery = { break; } - message.column.push(Column.decode(reader, reader.uint32())); + message.column.push(TypedColumn.decode(reader, reader.uint32())); continue; case 4: if (tag !== 34) { @@ -1481,14 +1866,14 @@ export const SelectQuery = { break; } - message.groupBy.push(reader.string()); + message.groupBy.push(Column.decode(reader, reader.uint32())); continue; case 6: if (tag !== 50) { break; } - message.orderBy.push(reader.string()); + message.orderBy.push(Column.decode(reader, reader.uint32())); continue; case 7: if (tag !== 58) { @@ -1522,19 +1907,19 @@ export const SelectQuery = { fromJSON(object: any): SelectQuery { return { - schema: isSet(object.schema) ? String(object.schema) : undefined, + schema: isSet(object.schema) ? String(object.schema) : "", table: isSet(object.table) ? String(object.table) : "", column: Array.isArray(object?.column) - ? object.column.map((e: any) => Column.fromJSON(e)) + ? object.column.map((e: any) => TypedColumn.fromJSON(e)) : [], where: Array.isArray(object?.where) ? object.where.map((e: any) => WhereCriteria.fromJSON(e)) : [], groupBy: Array.isArray(object?.groupBy) - ? object.groupBy.map((e: any) => String(e)) + ? object.groupBy.map((e: any) => Column.fromJSON(e)) : [], orderBy: Array.isArray(object?.orderBy) - ? object.orderBy.map((e: any) => String(e)) + ? object.orderBy.map((e: any) => Column.fromJSON(e)) : [], join: Array.isArray(object?.join) ? object.join.map((e: any) => Join.fromJSON(e)) @@ -1550,7 +1935,7 @@ export const SelectQuery = { message.table !== undefined && (obj.table = message.table); if (message.column) { obj.column = message.column.map((e) => - e ? Column.toJSON(e) : undefined + e ? TypedColumn.toJSON(e) : undefined ); } else { obj.column = []; @@ -1563,12 +1948,16 @@ export const SelectQuery = { obj.where = []; } if (message.groupBy) { - obj.groupBy = message.groupBy.map((e) => e); + obj.groupBy = message.groupBy.map((e) => + e ? Column.toJSON(e) : undefined + ); } else { obj.groupBy = []; } if (message.orderBy) { - obj.orderBy = message.orderBy.map((e) => e); + obj.orderBy = message.orderBy.map((e) => + e ? Column.toJSON(e) : undefined + ); } else { obj.orderBy = []; } @@ -1590,13 +1979,14 @@ export const SelectQuery = { object: I ): SelectQuery { const message = createBaseSelectQuery(); - message.schema = object.schema ?? undefined; + message.schema = object.schema ?? ""; message.table = object.table ?? ""; - message.column = object.column?.map((e) => Column.fromPartial(e)) || []; + message.column = + object.column?.map((e) => TypedColumn.fromPartial(e)) || []; message.where = object.where?.map((e) => WhereCriteria.fromPartial(e)) || []; - message.groupBy = object.groupBy?.map((e) => e) || []; - message.orderBy = object.orderBy?.map((e) => e) || []; + message.groupBy = object.groupBy?.map((e) => Column.fromPartial(e)) || []; + message.orderBy = object.orderBy?.map((e) => Column.fromPartial(e)) || []; message.join = object.join?.map((e) => Join.fromPartial(e)) || []; message.limit = object.limit ?? 0; message.offset = object.offset ?? 0; @@ -1687,7 +2077,7 @@ export const ColumnValue = { function createBaseInsertQuery(): InsertQuery { return { - schema: undefined, + schema: "", table: "", columnValue: [], idColumn: undefined, @@ -1702,7 +2092,7 @@ export const InsertQuery = { message: InsertQuery, writer: _m0.Writer = _m0.Writer.create() ): _m0.Writer { - if (message.schema !== undefined) { + if (message.schema !== "") { writer.uint32(10).string(message.schema); } if (message.table !== "") { @@ -1721,7 +2111,7 @@ export const InsertQuery = { writer.uint32(50).string(message.idTable); } for (const v of message.returningFields) { - writer.uint32(58).string(v!); + ReturningColumn.encode(v!, writer.uint32(58).fork()).ldelim(); } return writer; }, @@ -1781,7 +2171,9 @@ export const InsertQuery = { break; } - message.returningFields.push(reader.string()); + message.returningFields.push( + ReturningColumn.decode(reader, reader.uint32()) + ); continue; } if ((tag & 7) === 4 || tag === 0) { @@ -1794,7 +2186,7 @@ export const InsertQuery = { fromJSON(object: any): InsertQuery { return { - schema: isSet(object.schema) ? String(object.schema) : undefined, + schema: isSet(object.schema) ? String(object.schema) : "", table: isSet(object.table) ? String(object.table) : "", columnValue: Array.isArray(object?.columnValue) ? object.columnValue.map((e: any) => ColumnValue.fromJSON(e)) @@ -1805,7 +2197,7 @@ export const InsertQuery = { : undefined, idTable: isSet(object.idTable) ? String(object.idTable) : undefined, returningFields: Array.isArray(object?.returningFields) - ? object.returningFields.map((e: any) => String(e)) + ? object.returningFields.map((e: any) => ReturningColumn.fromJSON(e)) : [], }; }, @@ -1825,7 +2217,9 @@ export const InsertQuery = { message.idSequence !== undefined && (obj.idSequence = message.idSequence); message.idTable !== undefined && (obj.idTable = message.idTable); if (message.returningFields) { - obj.returningFields = message.returningFields.map((e) => e); + obj.returningFields = message.returningFields.map((e) => + e ? ReturningColumn.toJSON(e) : undefined + ); } else { obj.returningFields = []; } @@ -1840,14 +2234,15 @@ export const InsertQuery = { object: I ): InsertQuery { const message = createBaseInsertQuery(); - message.schema = object.schema ?? undefined; + message.schema = object.schema ?? ""; message.table = object.table ?? ""; message.columnValue = object.columnValue?.map((e) => ColumnValue.fromPartial(e)) || []; message.idColumn = object.idColumn ?? undefined; message.idSequence = object.idSequence ?? undefined; message.idTable = object.idTable ?? undefined; - message.returningFields = object.returningFields?.map((e) => e) || []; + message.returningFields = + object.returningFields?.map((e) => ReturningColumn.fromPartial(e)) || []; return message; }, }; @@ -1928,7 +2323,7 @@ export const BulkInsertQuery = { }; function createBaseUpdateQuery(): UpdateQuery { - return { schema: undefined, table: "", columnValue: [], where: [] }; + return { schema: "", table: "", columnValue: [], where: [] }; } export const UpdateQuery = { @@ -1936,7 +2331,7 @@ export const UpdateQuery = { message: UpdateQuery, writer: _m0.Writer = _m0.Writer.create() ): _m0.Writer { - if (message.schema !== undefined) { + if (message.schema !== "") { writer.uint32(10).string(message.schema); } if (message.table !== "") { @@ -1998,7 +2393,7 @@ export const UpdateQuery = { fromJSON(object: any): UpdateQuery { return { - schema: isSet(object.schema) ? String(object.schema) : undefined, + schema: isSet(object.schema) ? String(object.schema) : "", table: isSet(object.table) ? String(object.table) : "", columnValue: Array.isArray(object?.columnValue) ? object.columnValue.map((e: any) => ColumnValue.fromJSON(e)) @@ -2038,7 +2433,7 @@ export const UpdateQuery = { object: I ): UpdateQuery { const message = createBaseUpdateQuery(); - message.schema = object.schema ?? undefined; + message.schema = object.schema ?? ""; message.table = object.table ?? ""; message.columnValue = object.columnValue?.map((e) => ColumnValue.fromPartial(e)) || []; @@ -2049,7 +2444,7 @@ export const UpdateQuery = { }; function createBaseDeleteQuery(): DeleteQuery { - return { schema: undefined, table: "", where: [] }; + return { schema: "", table: "", where: [] }; } export const DeleteQuery = { @@ -2057,7 +2452,7 @@ export const DeleteQuery = { message: DeleteQuery, writer: _m0.Writer = _m0.Writer.create() ): _m0.Writer { - if (message.schema !== undefined) { + if (message.schema !== "") { writer.uint32(10).string(message.schema); } if (message.table !== "") { @@ -2109,7 +2504,7 @@ export const DeleteQuery = { fromJSON(object: any): DeleteQuery { return { - schema: isSet(object.schema) ? String(object.schema) : undefined, + schema: isSet(object.schema) ? String(object.schema) : "", table: isSet(object.table) ? String(object.table) : "", where: Array.isArray(object?.where) ? object.where.map((e: any) => WhereCriteria.fromJSON(e)) @@ -2139,7 +2534,7 @@ export const DeleteQuery = { object: I ): DeleteQuery { const message = createBaseDeleteQuery(); - message.schema = object.schema ?? undefined; + message.schema = object.schema ?? ""; message.table = object.table ?? ""; message.where = object.where?.map((e) => WhereCriteria.fromPartial(e)) || []; diff --git a/clients/client-relational/src/query/BaseQuery.ts b/clients/client-relational/src/query/BaseQuery.ts index e9f15a1..209d234 100644 --- a/clients/client-relational/src/query/BaseQuery.ts +++ b/clients/client-relational/src/query/BaseQuery.ts @@ -1,11 +1,9 @@ -import { Schema } from "../interfaces/Schema"; -import { ValueTypes } from "./QueryBuilder"; +import { Model } from "../interfaces/Model"; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export abstract class BaseQuery> { - protected schema: Schema; +export abstract class BaseQuery> { + protected model: Model; - constructor(schema: Schema) { - this.schema = schema; + constructor(schema: Model) { + this.model = schema; } } diff --git a/clients/client-relational/src/query/QueryBuilder.ts b/clients/client-relational/src/query/QueryBuilder.ts index 644ab2c..011a360 100644 --- a/clients/client-relational/src/query/QueryBuilder.ts +++ b/clients/client-relational/src/query/QueryBuilder.ts @@ -4,15 +4,18 @@ import { Query, Value as RelationalValue, WhereCriteria, - Column, Join as JoinModel, JoinType, + TypedColumn, + Table, } from "../models/data-access-layer/relational/relational"; import { BaseQuery } from "./BaseQuery"; import _ from "lodash"; -export type Join = Omit; +type Join = Omit & { + table: Table; +}; export type ConditionGroup = [ group: "and" | "or", @@ -20,9 +23,9 @@ export type ConditionGroup = [ ]; export type WhereCondition = [ - column: Column, + column: TypedColumn, operator: Operator, - value: ValueTypes + ...value: ValueTypes[] ]; export type WhereGroupCondition = WhereCondition | ConditionGroup; @@ -61,6 +64,11 @@ interface Select extends Build { offset: (offset: number) => Build; } +interface Insert extends Build { + return: (...returning: TypedColumn[]) => Build; + build: () => Query; +} + interface Update extends Build { where: (...inputs: WhereCondition) => WhereForModify; whereGroup: (...inputs: WhereGroupCondition[]) => WhereForModify; @@ -80,24 +88,17 @@ export function or(...conditions: WhereGroupCondition[]): ConditionGroup { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export class QueryBuilder< - T extends Record -> extends BaseQuery { +export class QueryBuilder> extends BaseQuery { #query: Query | null = null; - public select(...columns: Column[]): Select { + public select(...columns: TypedColumn[]): Select { this.#query = { query: { $case: "select", select: { - schema: this.schema.dbSchema, - table: this.schema.tableName, - column: columns.map((col) => ({ - schema: col.schema, - tableName: col.tableName, - name: col.name, - type: col.type, - })), + schema: this.model.schema, + table: this.model.tableName, + column: columns, where: [], join: [], groupBy: [], @@ -122,35 +123,35 @@ export class QueryBuilder< }; } - public insert(input: Partial): Build { + public insert(input: Partial): Insert { // TODO: This is a hack to get the create and modify audit columns, we need to either use the same "column name" or add a new property to the schema let createAuditColumnName = "create_date"; let updateAuditColumnName = "modify_date"; let tableHasCreateAudit = false; - if (this.schema.columns.createDate != null) { + if (this.model.columns.createDate != null) { tableHasCreateAudit = true; - createAuditColumnName = this.schema.columns.createDate.name; - } else if (this.schema.columns.createTime != null) { + createAuditColumnName = this.model.columns.createDate.name; + } else if (this.model.columns.createTime != null) { tableHasCreateAudit = true; - createAuditColumnName = this.schema.columns.createTime.name; + createAuditColumnName = this.model.columns.createTime.name; } - if (this.schema.columns.modifyDate != null) { - updateAuditColumnName = this.schema.columns.modifyDate.name; - } else if (this.schema.columns.modifyTime != null) { - updateAuditColumnName = this.schema.columns.modifyTime.name; - } else if (this.schema.columns.dateModified != null) { - updateAuditColumnName = this.schema.columns.dateModified.name; + if (this.model.columns.modifyDate != null) { + updateAuditColumnName = this.model.columns.modifyDate.name; + } else if (this.model.columns.modifyTime != null) { + updateAuditColumnName = this.model.columns.modifyTime.name; + } else if (this.model.columns.dateModified != null) { + updateAuditColumnName = this.model.columns.dateModified.name; } this.#query = { query: { $case: "insert", insert: { - schema: this.schema.dbSchema, - table: this.schema.tableName, + schema: this.model.schema, + table: this.model.tableName, columnValue: Object.entries(input) .filter( ([key, value]) => @@ -159,16 +160,13 @@ export class QueryBuilder< key !== "modifyDate" ) .map(([key, value]) => ({ - column: this.schema.columns[key]?.name ?? key, - value: this.toRelationalValue( - this.schema.columns[key].type, + column: this.model.columns[key].name, + value: this.toRelationalValueForUpdate( + this.model.columns[key].type, value as T[string] ), })), - idTable: this.schema.tableName, - idColumn: this.schema.idColumn ?? undefined, - idSequence: this.schema.idSequence ?? undefined, - returningFields: this.schema.returningFields ?? [], + returningFields: [], }, }, }; @@ -197,6 +195,7 @@ export class QueryBuilder< } return { + return: this.returning.bind(this), build: this.build.bind(this), }; } @@ -205,13 +204,13 @@ export class QueryBuilder< let updateAuditColumnName = "modify_date"; let auditColumnKey = "modifyDate"; - if (this.schema.columns.modifyDate != null) { - updateAuditColumnName = this.schema.columns.modifyDate.name; - } else if (this.schema.columns.modifyTime != null) { - updateAuditColumnName = this.schema.columns.modifyTime.name; + if (this.model.columns.modifyDate != null) { + updateAuditColumnName = this.model.columns.modifyDate.name; + } else if (this.model.columns.modifyTime != null) { + updateAuditColumnName = this.model.columns.modifyTime.name; auditColumnKey = "modifyTime"; - } else if (this.schema.columns.dateModified != null) { - updateAuditColumnName = this.schema.columns.dateModified.name; + } else if (this.model.columns.dateModified != null) { + updateAuditColumnName = this.model.columns.dateModified.name; auditColumnKey = "dateModified"; } @@ -219,8 +218,8 @@ export class QueryBuilder< query: { $case: "update", update: { - schema: this.schema.dbSchema, - table: this.schema.tableName, + schema: this.model.schema, + table: this.model.tableName, columnValue: [ { column: updateAuditColumnName, @@ -237,9 +236,9 @@ export class QueryBuilder< ([key, value]) => value !== undefined && key != auditColumnKey ), ([key, value]) => ({ - column: this.schema.columns[key]?.name ?? key, - value: this.toRelationalValue( - this.schema.columns[key].type, + column: this.model.columns[key].name, + value: this.toRelationalValueForUpdate( + this.model.columns[key].type, value as T[string] ), }) @@ -262,8 +261,8 @@ export class QueryBuilder< query: { $case: "delete", delete: { - schema: this.schema.dbSchema, - table: this.schema.tableName, + schema: this.model.schema, + table: this.model.tableName, where: [], }, }, @@ -422,7 +421,7 @@ export class QueryBuilder< } private buildForCondition(condition: WhereCondition): Partial { - const [column, operator, value] = condition; + const [column, operator, ...value] = condition; return { whereType: { @@ -459,6 +458,18 @@ export class QueryBuilder< }; } + private returning(...returningFields: TypedColumn[]): Build { + if (this.#query?.query?.$case != "insert") { + throw new Error("Cannot set returning fields on non-insert query"); + } + this.#query.query.insert.returningFields = _.map(returningFields, (rf) => + _.pick(rf, ["name", "type", "alias"]) + ); + return { + build: this.build.bind(this), + }; + } + private build(): Query { if (!this.#query) { throw new Error("Query has not been built yet."); @@ -476,7 +487,7 @@ export class QueryBuilder< ); } - private toRelationalValue( + private toRelationalValueForUpdate( columnType: ColumnType, value: ValueTypes ): RelationalValue | undefined { @@ -523,4 +534,72 @@ export class QueryBuilder< throw new Error(`Unsupported data type ${columnType}`); } + + private toRelationalValue( + columnType: ColumnType, + values: ValueTypes[] + ): RelationalValue[] { + // is null or set null + if (values.length === 0) { + throw new Error(`Value array should not be empty`); + } + + if (columnType === ColumnType.COLUMN_TYPE_INT) { + return _.map(values, (value) => { + return { value: { $case: "intValue", intValue: value as number } }; + }); + } + + if (columnType === ColumnType.COLUMN_TYPE_LONG) { + return _.map(values, (value) => { + return { value: { $case: "longValue", longValue: value as number } }; + }); + } + + if (columnType === ColumnType.COLUMN_TYPE_FLOAT) { + return _.map(values, (value) => { + return { value: { $case: "floatValue", floatValue: value as number } }; + }); + } + + if (columnType === ColumnType.COLUMN_TYPE_DOUBLE) { + return _.map(values, (value) => { + return { + value: { $case: "doubleValue", doubleValue: value as number }, + }; + }); + } + + if (columnType === ColumnType.COLUMN_TYPE_DATE) { + return _.map(values, (value) => { + return { value: { $case: "dateValue", dateValue: value as string } }; + }); + } + + if (columnType == ColumnType.COLUMN_TYPE_DATETIME) { + return _.map(values, (value) => { + return { + value: { $case: "datetimeValue", datetimeValue: value as string }, + }; + }); + } + + if (columnType == ColumnType.COLUMN_TYPE_STRING) { + return _.map(values, (value) => { + return { + value: { $case: "stringValue", stringValue: value as string }, + }; + }); + } + + if (columnType == ColumnType.COLUMN_TYPE_BOOLEAN) { + return _.map(values, (value) => { + return { + value: { $case: "booleanValue", booleanValue: value as boolean }, + }; + }); + } + + throw new Error(`Unsupported data type ${columnType}`); + } } From f5b11fc3e63698e425907e9678ef04e661f3fdb3 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Tue, 18 Jul 2023 00:51:22 +0300 Subject: [PATCH 12/12] refactor: add model --- clients/client-relational/.eslintrc | 5 +- .../src/common/ModelBuilder.ts | 22 - clients/client-relational/src/common/Op.ts | 9 + clients/client-relational/src/common/index.ts | 2 +- clients/client-relational/src/index.ts | 2 +- .../src/interfaces/Conditions.ts | 18 + .../src/interfaces/Joiner.ts | 8 + .../client-relational/src/interfaces/Model.ts | 7 - .../src/interfaces/ModelAttributes.ts | 5 + .../src/interfaces/ModelOptions.ts | 18 + .../src/interfaces/Schema.ts | 22 - .../src/interfaces/TableColumns.ts | 11 +- .../src/interfaces/ValueTypes.ts | 1 + .../client-relational/src/interfaces/index.ts | 7 +- .../client-relational/src/query/BaseQuery.ts | 9 - clients/client-relational/src/query/Model.ts | 569 ++++++++++++++++ .../src/query/QueryBuilder.ts | 605 ------------------ clients/client-relational/src/query/index.ts | 2 +- 18 files changed, 648 insertions(+), 674 deletions(-) delete mode 100644 clients/client-relational/src/common/ModelBuilder.ts create mode 100644 clients/client-relational/src/common/Op.ts create mode 100644 clients/client-relational/src/interfaces/Conditions.ts create mode 100644 clients/client-relational/src/interfaces/Joiner.ts delete mode 100644 clients/client-relational/src/interfaces/Model.ts create mode 100644 clients/client-relational/src/interfaces/ModelAttributes.ts create mode 100644 clients/client-relational/src/interfaces/ModelOptions.ts delete mode 100644 clients/client-relational/src/interfaces/Schema.ts create mode 100644 clients/client-relational/src/interfaces/ValueTypes.ts delete mode 100644 clients/client-relational/src/query/BaseQuery.ts create mode 100644 clients/client-relational/src/query/Model.ts delete mode 100644 clients/client-relational/src/query/QueryBuilder.ts diff --git a/clients/client-relational/.eslintrc b/clients/client-relational/.eslintrc index 87ab180..4395479 100644 --- a/clients/client-relational/.eslintrc +++ b/clients/client-relational/.eslintrc @@ -4,6 +4,9 @@ "project": "tsconfig.json" }, "rules": { - "@typescript-eslint/no-explicity-any": "off" + "@typescript-eslint/no-explicity-any": "off", + "@typescript-eslint/no-unsafe-assignment":"off", + "@typescript-eslint/no-unsafe-member-access":"off", + "@typescript-eslint/no-unsafe-argument":"off" } } diff --git a/clients/client-relational/src/common/ModelBuilder.ts b/clients/client-relational/src/common/ModelBuilder.ts deleted file mode 100644 index d1d18ac..0000000 --- a/clients/client-relational/src/common/ModelBuilder.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Schema } from "../interfaces/Schema"; -import _ from "lodash"; -import { Model } from "src/interfaces/Model"; - -export function constructModel>( - schema: Schema -): Model { - const model: Model = { - schema: schema.dbSchema, - tableName: schema.tableName, - columns: _.mapValues(schema.columns, (col, key) => { - return { - schema: schema.dbSchema, - tableName: schema.tableName, - name: col.name, - alias: key, - type: col.type, - }; - }), - }; - return model; -} diff --git a/clients/client-relational/src/common/Op.ts b/clients/client-relational/src/common/Op.ts new file mode 100644 index 0000000..84db57e --- /dev/null +++ b/clients/client-relational/src/common/Op.ts @@ -0,0 +1,9 @@ +import { WhereGroupCondition, ConditionGroup } from "src/interfaces"; + +export function and(...conditions: WhereGroupCondition[]): ConditionGroup { + return ["and", conditions]; +} + +export function or(...conditions: WhereGroupCondition[]): ConditionGroup { + return ["or", conditions]; +} diff --git a/clients/client-relational/src/common/index.ts b/clients/client-relational/src/common/index.ts index 2e40db4..a9da38d 100644 --- a/clients/client-relational/src/common/index.ts +++ b/clients/client-relational/src/common/index.ts @@ -1 +1 @@ -export * from "./ModelBuilder"; +export * from "./Op"; diff --git a/clients/client-relational/src/index.ts b/clients/client-relational/src/index.ts index dc6a5b8..b1d81ce 100644 --- a/clients/client-relational/src/index.ts +++ b/clients/client-relational/src/index.ts @@ -1,5 +1,5 @@ export * from "./client/RelationalClient"; +export * from "./common"; export * from "./interfaces/"; export * from "./models/data-access-layer/relational/relational"; export * from "./query/"; -export * from "./common"; diff --git a/clients/client-relational/src/interfaces/Conditions.ts b/clients/client-relational/src/interfaces/Conditions.ts new file mode 100644 index 0000000..6850703 --- /dev/null +++ b/clients/client-relational/src/interfaces/Conditions.ts @@ -0,0 +1,18 @@ +import { + Operator, + TypedColumn, +} from "src/models/data-access-layer/relational/relational"; +import { ValueTypes } from "./ValueTypes"; + +export type ConditionGroup = [ + group: "and" | "or", + conditions: WhereGroupCondition[] +]; + +export type WhereCondition = [ + column: TypedColumn, + operator: Operator, + ...value: ValueTypes[] +]; + +export type WhereGroupCondition = WhereCondition | ConditionGroup; diff --git a/clients/client-relational/src/interfaces/Joiner.ts b/clients/client-relational/src/interfaces/Joiner.ts new file mode 100644 index 0000000..af9297d --- /dev/null +++ b/clients/client-relational/src/interfaces/Joiner.ts @@ -0,0 +1,8 @@ +import { + Join, + JoinCondition, +} from "src/models/data-access-layer/relational/relational"; + +export type Joiner = Omit, "type"> & { + conditions: Required[]; +}; diff --git a/clients/client-relational/src/interfaces/Model.ts b/clients/client-relational/src/interfaces/Model.ts deleted file mode 100644 index 2ae91a0..0000000 --- a/clients/client-relational/src/interfaces/Model.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { TableColumns } from "./TableColumns"; - -export type Model> = { - schema: string; - tableName: string; - columns: TableColumns; -}; diff --git a/clients/client-relational/src/interfaces/ModelAttributes.ts b/clients/client-relational/src/interfaces/ModelAttributes.ts new file mode 100644 index 0000000..be5e2ff --- /dev/null +++ b/clients/client-relational/src/interfaces/ModelAttributes.ts @@ -0,0 +1,5 @@ +import { TypedColumn } from "src/models/data-access-layer/relational/relational"; + +export type ModelAttributes = { + [Property in keyof T]-?: TypedColumn; +}; diff --git a/clients/client-relational/src/interfaces/ModelOptions.ts b/clients/client-relational/src/interfaces/ModelOptions.ts new file mode 100644 index 0000000..66cfb71 --- /dev/null +++ b/clients/client-relational/src/interfaces/ModelOptions.ts @@ -0,0 +1,18 @@ +type BaseModelOptions = { + schema: string; + table: string; +}; + +type ModelOptionsWithoutTimestamps = BaseModelOptions & { + timestamps?: false; +}; + +type ModelOptionsWithTimestamps = BaseModelOptions & { + timestamps: true; + createdAt: keyof T; + updatedAt: keyof T; +}; + +export type ModelOptions = + | ModelOptionsWithoutTimestamps + | ModelOptionsWithTimestamps; diff --git a/clients/client-relational/src/interfaces/Schema.ts b/clients/client-relational/src/interfaces/Schema.ts deleted file mode 100644 index 14a5b77..0000000 --- a/clients/client-relational/src/interfaces/Schema.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - ColumnType, - TypedColumn, -} from "../models/data-access-layer/relational/relational"; - -export type Schema> = { - dbSchema: string; - tableName: string; - idColumn?: string; - idSequence?: string; - idTable?: string; - columns: TableColumns; -}; - -type TableColumn = Partial & { - name: string; - type: ColumnType; -}; - -type TableColumns> = { - [Property in keyof T]: TableColumn; -}; diff --git a/clients/client-relational/src/interfaces/TableColumns.ts b/clients/client-relational/src/interfaces/TableColumns.ts index fc304a2..a9fb97c 100644 --- a/clients/client-relational/src/interfaces/TableColumns.ts +++ b/clients/client-relational/src/interfaces/TableColumns.ts @@ -1,5 +1,10 @@ -import { TypedColumn } from "../models/data-access-layer/relational/relational"; +import { ColumnType } from "src/models/data-access-layer/relational/relational"; -export type TableColumns> = { - [Property in keyof T]: TypedColumn; +type TableColumn = { + name: string; + type: ColumnType; +}; + +export type TableColumns = { + [Property in keyof T]-?: TableColumn; }; diff --git a/clients/client-relational/src/interfaces/ValueTypes.ts b/clients/client-relational/src/interfaces/ValueTypes.ts new file mode 100644 index 0000000..5ad3250 --- /dev/null +++ b/clients/client-relational/src/interfaces/ValueTypes.ts @@ -0,0 +1 @@ +export type ValueTypes = string | number | boolean | null; diff --git a/clients/client-relational/src/interfaces/index.ts b/clients/client-relational/src/interfaces/index.ts index 767b302..9a38c96 100644 --- a/clients/client-relational/src/interfaces/index.ts +++ b/clients/client-relational/src/interfaces/index.ts @@ -1,3 +1,6 @@ -export * from "./Schema"; +export * from "./Conditions"; +export * from "./Joiner"; +export * from "./ModelAttributes"; +export * from "./ModelOptions"; export * from "./TableColumns"; -export * from "./Model"; +export * from "./ValueTypes"; diff --git a/clients/client-relational/src/query/BaseQuery.ts b/clients/client-relational/src/query/BaseQuery.ts deleted file mode 100644 index 209d234..0000000 --- a/clients/client-relational/src/query/BaseQuery.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Model } from "../interfaces/Model"; - -export abstract class BaseQuery> { - protected model: Model; - - constructor(schema: Model) { - this.model = schema; - } -} diff --git a/clients/client-relational/src/query/Model.ts b/clients/client-relational/src/query/Model.ts new file mode 100644 index 0000000..eb6a94a --- /dev/null +++ b/clients/client-relational/src/query/Model.ts @@ -0,0 +1,569 @@ +import _ from "lodash"; +import { + ColumnType, + JoinType, + Query, + TypedColumn, + Value as RelationalValue, + WhereCriteria, + SelectQuery, + InsertQuery, + UpdateQuery, + DeleteQuery, +} from "../models/data-access-layer/relational/relational"; +import { + ModelAttributes, + TableColumns, + ModelOptions, + WhereGroupCondition, + WhereCondition, + ConditionGroup, + ValueTypes, + Joiner, +} from "src/interfaces"; + +interface Build { + build: () => Query; +} + +interface Select extends Build { + join: (join: Joiner) => Select; + leftJoin: (join: Joiner) => Select; + rightJoin: (join: Joiner) => Select; + fullJoin: (join: Joiner) => Select; + where: (...inputs: WhereCondition) => Where; + whereGroup: (...inputs: WhereGroupCondition[]) => Where; + orderBy: (...order: TypedColumn[]) => OrderBy; + limit: (limit: number) => Limit; + offset: (offset: number) => Build; +} + +interface Insert extends Build { + return: (...returning: TypedColumn[]) => Build; + build: () => Query; +} + +interface Update extends Build { + where: (...inputs: WhereCondition) => WhereForModify; + whereGroup: (...inputs: WhereGroupCondition[]) => WhereForModify; +} + +interface Delete { + where: (...inputs: WhereCondition) => WhereForModify; + whereGroup: (...inputs: WhereGroupCondition[]) => WhereForModify; +} + +interface OrderBy extends Build { + limit: (limit: number) => Limit; + offset: (offset: number) => Build; + build: () => Query; +} + +interface Limit extends Build { + offset: (offset: number) => Build; + build: () => Query; +} + +interface Where extends Build { + andWhere: (...inputs: WhereCondition) => Where; + andWhereGroup: (...inputs: WhereGroupCondition[]) => Where; + orderBy: (...order: TypedColumn[]) => OrderBy; + limit: (limit: number) => Limit; + offset: (offset: number) => Build; +} + +interface WhereForModify extends Build { + andWhere: (...inputs: WhereCondition) => WhereForModify; + andWhereGroup: (...inputs: WhereGroupCondition[]) => WhereForModify; +} + +type PartialOrNull = { + [P in keyof T]?: T[P] | null; +}; + +export class Model< + U = never, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T extends U extends Record ? any : any = U +> { + private schema: string; + private table: string; + private timestamps: boolean; + private createdAt?: TypedColumn; + private updatedAt?: TypedColumn; + public attributes: ModelAttributes; + + constructor(attributes: TableColumns, options: ModelOptions) { + this.schema = options.schema; + this.table = options.table; + this.timestamps = options.timestamps ?? false; + this.attributes = _.mapValues(attributes, (col, key) => { + return { + schema: this.schema, + tableName: this.table, + name: col.name, + alias: key, + type: col.type, + }; + }); + if (options.timestamps) { + this.createdAt = this.attributes[options.createdAt]; + this.updatedAt = this.attributes[options.updatedAt]; + } + } + + public select(...columns: TypedColumn[]): Select { + const query = { + schema: this.schema, + table: this.table, + column: columns, + where: [], + join: [], + groupBy: [], + orderBy: [], + limit: 100, + offset: 0, + }; + + return { + join: this.join.bind(this, query, JoinType.JOIN_TYPE_INNER), + leftJoin: this.join.bind(this, query, JoinType.JOIN_TYPE_LEFT), + rightJoin: this.join.bind(this, query, JoinType.JOIN_TYPE_RIGHT), + fullJoin: this.join.bind(this, query, JoinType.JOIN_TYPE_FULL), + where: this.where.bind(this, query), + whereGroup: this.whereGroup.bind(this, query), + orderBy: this.orderBy.bind(this, query), + limit: this.limit.bind(this, query), + offset: this.offset.bind(this, query), + build: this.build.bind(this, query, "select"), + }; + } + + public insert(input: Partial): Insert { + const query = { + schema: this.schema, + table: this.table, + columnValue: _.map( + _.filter( + _.entries(input), + ([key, value]) => + value !== undefined && + key !== this.createdAt?.alias && + key !== this.updatedAt?.alias + ), + ([key, value]) => ({ + column: this.attributes[key].name, + value: this.toRelationalValueForUpdate( + this.attributes[key].type, + value as T[string] + ), + }) + ), + returningFields: [], + }; + + if (this.timestamps && this.createdAt && this.updatedAt) { + query.columnValue.push( + { + column: this.createdAt.name, + value: { + value: { + $case: "datetimeValue", + // $NOW will be converted to relevant expression in data access later + datetimeValue: "$NOW", + }, + }, + }, + { + column: this.updatedAt.name, + value: { + value: { + $case: "datetimeValue", + datetimeValue: "$NOW", + }, + }, + } + ); + } + + return { + return: this.returning.bind(this, query), + build: this.build.bind(this, query, "insert"), + }; + } + + public update(input: PartialOrNull): Update { + const query = { + schema: this.schema, + table: this.table, + columnValue: _.map( + _.filter( + _.entries(input), + ([key, value]) => + value !== undefined && + key !== this.createdAt?.alias && + key !== this.updatedAt?.alias + ), + ([key, value]) => ({ + column: this.attributes[key].name, + value: this.toRelationalValueForUpdate( + this.attributes[key].type, + value as T[string] + ), + }) + ), + where: [], + }; + + if (this.timestamps && this.updatedAt) { + query.columnValue.push({ + column: this.updatedAt.name, + value: { + value: { + $case: "datetimeValue", + datetimeValue: "$NOW", + }, + }, + }); + } + + return { + where: this.whereForModify.bind(this, query, "update"), + whereGroup: this.whereGroupForModify.bind(this, query, "update"), + build: this.build.bind(this, query, "update"), + }; + } + + public delete(): Delete { + const query = { + schema: this.schema, + table: this.table, + where: [], + }; + + return { + where: this.whereForModify.bind(this, query, "delete"), + whereGroup: this.whereGroupForModify.bind(this, query, "delete"), + }; + } + + private join(query: SelectQuery, type: JoinType, join: Joiner): Select { + query.join.push(_.assign({}, join, { type })); + return { + join: this.join.bind(this, query, JoinType.JOIN_TYPE_INNER), + leftJoin: this.join.bind(this, query, JoinType.JOIN_TYPE_LEFT), + rightJoin: this.join.bind(this, query, JoinType.JOIN_TYPE_RIGHT), + fullJoin: this.join.bind(this, query, JoinType.JOIN_TYPE_FULL), + where: this.where.bind(this, query), + whereGroup: this.whereGroup.bind(this, query), + orderBy: this.orderBy.bind(this, query), + limit: this.limit.bind(this, query), + offset: this.offset.bind(this, query), + build: this.build.bind(this, query, "select"), + }; + } + + private where(query: SelectQuery, ...inputs: WhereCondition): Where { + if (!inputs.length) { + throw new Error("Where requires at least one argument"); + } + + query.where.push(this.buildForCondition(inputs)); + + return { + andWhere: this.where.bind(this, query), + andWhereGroup: this.whereGroup.bind(this, query), + orderBy: this.orderBy.bind(this, query), + limit: this.limit.bind(this, query), + offset: this.offset.bind(this, query), + build: this.build.bind(this, query, "select"), + }; + } + + private whereGroup( + query: SelectQuery, + ...inputs: WhereGroupCondition[] + ): Where { + if (!inputs.length) { + throw new Error("Where requires at least one argument"); + } + + for (const input of inputs) { + if (this.isConditionGroup(input)) { + query.where.push(this.buildForConditionGroup(input)); + } else { + query.where.push(this.buildForCondition(input)); + } + } + + return { + andWhere: this.where.bind(this, query), + andWhereGroup: this.whereGroup.bind(this, query), + orderBy: this.orderBy.bind(this, query), + limit: this.limit.bind(this, query), + offset: this.offset.bind(this, query), + build: this.build.bind(this, query, "select"), + }; + } + + private whereForModify( + query: UpdateQuery | DeleteQuery, + qType: "update" | "delete", + ...inputs: WhereCondition + ): WhereForModify { + if (!inputs.length) { + throw new Error("Where requires at least one argument"); + } + + query.where.push(this.buildForCondition(inputs)); + return { + andWhere: this.whereForModify.bind(this, query, qType), + andWhereGroup: this.whereGroupForModify.bind(this, query, qType), + build: this.build.bind(this, query, qType), + }; + } + + private whereGroupForModify( + query: UpdateQuery | DeleteQuery, + qType: "update" | "delete", + ...inputs: WhereGroupCondition[] + ): WhereForModify { + if (!inputs.length) { + throw new Error("Where requires at least one argument"); + } + + for (const input of inputs) { + if (this.isConditionGroup(input)) { + query.where.push(this.buildForConditionGroup(input)); + } else { + query.where.push(this.buildForCondition(input)); + } + } + + return { + andWhere: this.whereForModify.bind(this, query, qType), + andWhereGroup: this.whereGroupForModify.bind(this, query, qType), + build: this.build.bind(this, query, qType), + }; + } + + private buildForConditionGroup( + conditionGroup: ConditionGroup + ): Partial { + const [group, conditions] = conditionGroup; + + const wheres: WhereCriteria[] = _.map(conditions, (condition) => { + if (this.isConditionGroup(condition)) { + return this.buildForConditionGroup(condition); + } else { + return this.buildForCondition(condition); + } + }); + + const whereType: Partial = {}; + if (group === "and") { + whereType.whereType = { $case: "and", and: { where: wheres } }; + } else { + whereType.whereType = { $case: "or", or: { where: wheres } }; + } + + return whereType; + } + + private buildForCondition(condition: WhereCondition): Partial { + const [column, operator, ...value] = condition; + + return { + whereType: { + $case: "condition", + condition: { + key: column, + operator, + value: this.toRelationalValue(column.type, value), + }, + }, + }; + } + + private orderBy(query: SelectQuery, ...order: TypedColumn[]): OrderBy { + query.orderBy = _.map(order, (o) => + _.pick(o, ["schema", "tableName", "name"]) + ); + + return { + limit: this.limit.bind(this, query), + offset: this.offset.bind(this, query), + build: this.build.bind(this, query, "select"), + }; + } + + private limit(query: SelectQuery, limit: number): Limit { + query.limit = limit; + + return { + offset: this.offset.bind(this, query), + build: this.build.bind(this, query, "select"), + }; + } + + private offset(query: SelectQuery, offset: number): Build { + query.offset = offset; + + return { + build: this.build.bind(this, query, "select"), + }; + } + + private returning( + query: InsertQuery, + ...returningFields: TypedColumn[] + ): Build { + query.returningFields = _.map(returningFields, (rf) => + _.pick(rf, ["name", "type", "alias"]) + ); + return { + build: this.build.bind(this, query, "insert"), + }; + } + + private build( + query: SelectQuery | InsertQuery | UpdateQuery | DeleteQuery, + qType: "select" | "insert" | "update" | "delete" + ): Query { + if (qType === "select") { + return { query: { $case: qType, [qType]: query as SelectQuery } }; + } else if (qType === "insert") { + return { query: { $case: qType, [qType]: query as InsertQuery } }; + } else if (qType === "update") { + return { query: { $case: qType, [qType]: query as UpdateQuery } }; + } else if (qType === "delete") { + return { query: { $case: qType, [qType]: query as DeleteQuery } }; + } + throw new Error("This shouldn't happen"); + } + + private isConditionGroup(criteria: unknown): criteria is ConditionGroup { + return ( + _.isArray(criteria) && + criteria.length === 2 && + criteria[0] in ["and", "or"] && + _.isArray(criteria[2]) + ); + } + + private toRelationalValueForUpdate( + columnType: ColumnType, + value: ValueTypes + ): RelationalValue | undefined { + // is null or set null + if (value === null) { + return undefined; + } + + if (columnType === ColumnType.COLUMN_TYPE_INT) { + return { value: { $case: "intValue", intValue: value as number } }; + } + + if (columnType === ColumnType.COLUMN_TYPE_LONG) { + return { value: { $case: "longValue", longValue: value as number } }; + } + + if (columnType === ColumnType.COLUMN_TYPE_FLOAT) { + return { value: { $case: "floatValue", floatValue: value as number } }; + } + + if (columnType === ColumnType.COLUMN_TYPE_DOUBLE) { + return { value: { $case: "doubleValue", doubleValue: value as number } }; + } + + if (columnType === ColumnType.COLUMN_TYPE_DATE) { + return { value: { $case: "dateValue", dateValue: value as string } }; + } + + if (columnType == ColumnType.COLUMN_TYPE_DATETIME) { + return { + value: { $case: "datetimeValue", datetimeValue: value as string }, + }; + } + + if (columnType == ColumnType.COLUMN_TYPE_STRING) { + return { value: { $case: "stringValue", stringValue: value as string } }; + } + + if (columnType == ColumnType.COLUMN_TYPE_BOOLEAN) { + return { + value: { $case: "booleanValue", booleanValue: value as boolean }, + }; + } + + throw new Error(`Unsupported data type ${columnType}`); + } + + private toRelationalValue( + columnType: ColumnType, + values: ValueTypes[] + ): RelationalValue[] { + // is null or set null + if (values.length === 0) { + throw new Error(`Value array should not be empty`); + } + + if (columnType === ColumnType.COLUMN_TYPE_INT) { + return _.map(values, (value) => { + return { value: { $case: "intValue", intValue: value as number } }; + }); + } + + if (columnType === ColumnType.COLUMN_TYPE_LONG) { + return _.map(values, (value) => { + return { value: { $case: "longValue", longValue: value as number } }; + }); + } + + if (columnType === ColumnType.COLUMN_TYPE_FLOAT) { + return _.map(values, (value) => { + return { value: { $case: "floatValue", floatValue: value as number } }; + }); + } + + if (columnType === ColumnType.COLUMN_TYPE_DOUBLE) { + return _.map(values, (value) => { + return { + value: { $case: "doubleValue", doubleValue: value as number }, + }; + }); + } + + if (columnType === ColumnType.COLUMN_TYPE_DATE) { + return _.map(values, (value) => { + return { value: { $case: "dateValue", dateValue: value as string } }; + }); + } + + if (columnType == ColumnType.COLUMN_TYPE_DATETIME) { + return _.map(values, (value) => { + return { + value: { $case: "datetimeValue", datetimeValue: value as string }, + }; + }); + } + + if (columnType == ColumnType.COLUMN_TYPE_STRING) { + return _.map(values, (value) => { + return { + value: { $case: "stringValue", stringValue: value as string }, + }; + }); + } + + if (columnType == ColumnType.COLUMN_TYPE_BOOLEAN) { + return _.map(values, (value) => { + return { + value: { $case: "booleanValue", booleanValue: value as boolean }, + }; + }); + } + + throw new Error(`Unsupported data type ${columnType}`); + } +} diff --git a/clients/client-relational/src/query/QueryBuilder.ts b/clients/client-relational/src/query/QueryBuilder.ts deleted file mode 100644 index 011a360..0000000 --- a/clients/client-relational/src/query/QueryBuilder.ts +++ /dev/null @@ -1,605 +0,0 @@ -import { - ColumnType, - Operator, - Query, - Value as RelationalValue, - WhereCriteria, - Join as JoinModel, - JoinType, - TypedColumn, - Table, -} from "../models/data-access-layer/relational/relational"; - -import { BaseQuery } from "./BaseQuery"; -import _ from "lodash"; - -type Join = Omit & { - table: Table; -}; - -export type ConditionGroup = [ - group: "and" | "or", - conditions: WhereGroupCondition[] -]; - -export type WhereCondition = [ - column: TypedColumn, - operator: Operator, - ...value: ValueTypes[] -]; - -export type WhereGroupCondition = WhereCondition | ConditionGroup; - -export type ValueTypes = string | number | boolean | null; - -interface Build { - build: () => Query; -} - -interface Limit extends Build { - offset: (offset: number) => Build; - build: () => Query; -} - -interface Where extends Build { - andWhere: (...inputs: WhereCondition) => Where; - andWhereGroup: (...inputs: WhereGroupCondition[]) => Where; - limit: (limit: number) => Limit; - offset: (offset: number) => Build; -} - -interface WhereForModify extends Build { - andWhere: (...inputs: WhereCondition) => WhereForModify; - andWhereGroup: (...inputs: WhereGroupCondition[]) => WhereForModify; -} - -interface Select extends Build { - join: (join: Join) => Select; - leftJoin: (join: Join) => Select; - rightJoin: (join: Join) => Select; - fullJoin: (join: Join) => Select; - where: (...inputs: WhereCondition) => Where; - whereGroup: (...inputs: WhereGroupCondition[]) => Where; - limit: (limit: number) => Limit; - offset: (offset: number) => Build; -} - -interface Insert extends Build { - return: (...returning: TypedColumn[]) => Build; - build: () => Query; -} - -interface Update extends Build { - where: (...inputs: WhereCondition) => WhereForModify; - whereGroup: (...inputs: WhereGroupCondition[]) => WhereForModify; -} - -interface Delete { - where: (...inputs: WhereCondition) => WhereForModify; - whereGroup: (...inputs: WhereGroupCondition[]) => WhereForModify; -} - -export function and(...conditions: WhereGroupCondition[]): ConditionGroup { - return ["and", conditions]; -} - -export function or(...conditions: WhereGroupCondition[]): ConditionGroup { - return ["or", conditions]; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export class QueryBuilder> extends BaseQuery { - #query: Query | null = null; - - public select(...columns: TypedColumn[]): Select { - this.#query = { - query: { - $case: "select", - select: { - schema: this.model.schema, - table: this.model.tableName, - column: columns, - where: [], - join: [], - groupBy: [], - orderBy: [], - limit: 100, - offset: 0, - }, - }, - }; - - return { - join: this.join.bind(this, JoinType.JOIN_TYPE_INNER), - leftJoin: this.join.bind(this, JoinType.JOIN_TYPE_LEFT), - rightJoin: this.join.bind(this, JoinType.JOIN_TYPE_RIGHT), - fullJoin: this.join.bind(this, JoinType.JOIN_TYPE_FULL), - where: this.where.bind(this), - whereGroup: this.whereGroup.bind(this), - // orderBy: this.orderBy.bind(this), - limit: this.limit.bind(this), - offset: this.offset.bind(this), - build: this.build.bind(this), - }; - } - - public insert(input: Partial): Insert { - // TODO: This is a hack to get the create and modify audit columns, we need to either use the same "column name" or add a new property to the schema - let createAuditColumnName = "create_date"; - let updateAuditColumnName = "modify_date"; - - let tableHasCreateAudit = false; - - if (this.model.columns.createDate != null) { - tableHasCreateAudit = true; - createAuditColumnName = this.model.columns.createDate.name; - } else if (this.model.columns.createTime != null) { - tableHasCreateAudit = true; - createAuditColumnName = this.model.columns.createTime.name; - } - - if (this.model.columns.modifyDate != null) { - updateAuditColumnName = this.model.columns.modifyDate.name; - } else if (this.model.columns.modifyTime != null) { - updateAuditColumnName = this.model.columns.modifyTime.name; - } else if (this.model.columns.dateModified != null) { - updateAuditColumnName = this.model.columns.dateModified.name; - } - - this.#query = { - query: { - $case: "insert", - insert: { - schema: this.model.schema, - table: this.model.tableName, - columnValue: Object.entries(input) - .filter( - ([key, value]) => - value !== undefined && - key !== "createDate" && - key !== "modifyDate" - ) - .map(([key, value]) => ({ - column: this.model.columns[key].name, - value: this.toRelationalValueForUpdate( - this.model.columns[key].type, - value as T[string] - ), - })), - returningFields: [], - }, - }, - }; - - if (tableHasCreateAudit && this.#query.query?.$case === "insert") { - this.#query.query.insert.columnValue.push( - { - column: createAuditColumnName, - value: { - value: { - $case: "datetimeValue", - datetimeValue: "CURRENT", - }, - }, - }, - { - column: updateAuditColumnName, - value: { - value: { - $case: "datetimeValue", - datetimeValue: "CURRENT", - }, - }, - } - ); - } - - return { - return: this.returning.bind(this), - build: this.build.bind(this), - }; - } - - public update(input: Partial): Update { - let updateAuditColumnName = "modify_date"; - let auditColumnKey = "modifyDate"; - - if (this.model.columns.modifyDate != null) { - updateAuditColumnName = this.model.columns.modifyDate.name; - } else if (this.model.columns.modifyTime != null) { - updateAuditColumnName = this.model.columns.modifyTime.name; - auditColumnKey = "modifyTime"; - } else if (this.model.columns.dateModified != null) { - updateAuditColumnName = this.model.columns.dateModified.name; - auditColumnKey = "dateModified"; - } - - this.#query = { - query: { - $case: "update", - update: { - schema: this.model.schema, - table: this.model.tableName, - columnValue: [ - { - column: updateAuditColumnName, - value: { - value: { - $case: "datetimeValue", - datetimeValue: "CURRENT", - }, - }, - }, - ..._.map( - _.filter( - _.entries(input), - ([key, value]) => value !== undefined && key != auditColumnKey - ), - ([key, value]) => ({ - column: this.model.columns[key].name, - value: this.toRelationalValueForUpdate( - this.model.columns[key].type, - value as T[string] - ), - }) - ), - ], - where: [], - }, - }, - }; - - return { - where: this.whereForModify.bind(this), - whereGroup: this.whereGroupForModify.bind(this), - build: this.build.bind(this), - }; - } - - public delete(): Delete { - this.#query = { - query: { - $case: "delete", - delete: { - schema: this.model.schema, - table: this.model.tableName, - where: [], - }, - }, - }; - - return { - where: this.whereForModify.bind(this), - whereGroup: this.whereGroupForModify.bind(this), - }; - } - - private join(type: JoinType, join: Join): Select { - if (this.#query?.query?.$case !== "select") { - throw new Error("Where can only be used in select queries"); - } - this.#query?.query.select.join.push(_.assign({}, join, { type })); - return { - join: this.join.bind(this, JoinType.JOIN_TYPE_INNER), - leftJoin: this.join.bind(this, JoinType.JOIN_TYPE_LEFT), - rightJoin: this.join.bind(this, JoinType.JOIN_TYPE_RIGHT), - fullJoin: this.join.bind(this, JoinType.JOIN_TYPE_FULL), - where: this.where.bind(this), - whereGroup: this.whereGroup.bind(this), - // orderBy: this.orderBy.bind(this), - limit: this.limit.bind(this), - offset: this.offset.bind(this), - build: this.build.bind(this), - }; - } - - // private orderBy(column: TableColumn, direction: "asc" | "desc") {} - - private where(...inputs: WhereCondition): Where { - if (this.#query?.query?.$case !== "select") { - throw new Error("Where can only be used in select queries"); - } - - if (!inputs.length) { - throw new Error("Where requires at least one argument"); - } - - this.#query.query.select.where.push(this.buildForCondition(inputs)); - - return { - andWhere: this.where.bind(this), - andWhereGroup: this.whereGroup.bind(this), - // orderBy: this.orderBy.bind(this), - limit: this.limit.bind(this), - offset: this.offset.bind(this), - build: this.build.bind(this), - }; - } - - private whereGroup(...inputs: WhereGroupCondition[]): Where { - if (this.#query?.query?.$case !== "select") { - throw new Error("Where can only be used in select queries"); - } - - if (!inputs.length) { - throw new Error("Where requires at least one argument"); - } - - for (const input of inputs) { - if (this.isConditionGroup(input)) { - this.#query.query.select.where.push(this.buildForConditionGroup(input)); - } else { - this.#query.query.select.where.push(this.buildForCondition(input)); - } - } - - return { - andWhere: this.where.bind(this), - andWhereGroup: this.whereGroup.bind(this), - // orderBy: this.orderBy.bind(this), - limit: this.limit.bind(this), - offset: this.offset.bind(this), - build: this.build.bind(this), - }; - } - - private whereForModify(...inputs: WhereCondition): WhereForModify { - if (!inputs.length) { - throw new Error("Where requires at least one argument"); - } - - if (this.#query?.query?.$case === "update") { - this.#query.query.update.where.push(this.buildForCondition(inputs)); - } else if (this.#query?.query?.$case === "delete") { - this.#query.query.delete.where.push(this.buildForCondition(inputs)); - } - - return { - andWhere: this.whereForModify.bind(this), - andWhereGroup: this.whereGroup.bind(this), - build: this.build.bind(this), - }; - } - - private whereGroupForModify( - ...inputs: WhereGroupCondition[] - ): WhereForModify { - if (!inputs.length) { - throw new Error("Where requires at least one argument"); - } - - if (this.#query?.query?.$case === "update") { - for (const input of inputs) { - if (this.isConditionGroup(input)) { - this.#query.query.update.where.push( - this.buildForConditionGroup(input) - ); - } else { - this.#query.query.update.where.push(this.buildForCondition(input)); - } - } - } else if (this.#query?.query?.$case === "delete") { - for (const input of inputs) { - if (this.isConditionGroup(input)) { - this.#query.query.delete.where.push( - this.buildForConditionGroup(input) - ); - } else { - this.#query.query.delete.where.push(this.buildForCondition(input)); - } - } - } - - return { - andWhere: this.whereForModify.bind(this), - andWhereGroup: this.whereGroup.bind(this), - build: this.build.bind(this), - }; - } - - private buildForConditionGroup( - conditionGroup: ConditionGroup - ): Partial { - const [group, conditions] = conditionGroup; - - const wheres: WhereCriteria[] = _.map(conditions, (condition) => { - if (this.isConditionGroup(condition)) { - return this.buildForConditionGroup(condition); - } else { - return this.buildForCondition(condition); - } - }); - - const whereType: Partial = {}; - if (group === "and") { - whereType.whereType = { $case: "and", and: { where: wheres } }; - } else { - whereType.whereType = { $case: "or", or: { where: wheres } }; - } - - return whereType; - } - - private buildForCondition(condition: WhereCondition): Partial { - const [column, operator, ...value] = condition; - - return { - whereType: { - $case: "condition", - condition: { - key: column, - operator, - value: this.toRelationalValue(column.type, value), - }, - }, - }; - } - - private limit(limit: number): Limit { - if (this.#query?.query?.$case != "select") { - throw new Error("Cannot set limit on a non-select query"); - } - this.#query.query.select.limit = limit; - - return { - offset: this.offset.bind(this), - build: this.build.bind(this), - }; - } - - private offset(offset: number): Build { - if (this.#query?.query?.$case != "select") { - throw new Error("Cannot set offset on a non-select query"); - } - this.#query.query.select.offset = offset; - - return { - build: this.build.bind(this), - }; - } - - private returning(...returningFields: TypedColumn[]): Build { - if (this.#query?.query?.$case != "insert") { - throw new Error("Cannot set returning fields on non-insert query"); - } - this.#query.query.insert.returningFields = _.map(returningFields, (rf) => - _.pick(rf, ["name", "type", "alias"]) - ); - return { - build: this.build.bind(this), - }; - } - - private build(): Query { - if (!this.#query) { - throw new Error("Query has not been built yet."); - } - - return this.#query; - } - - private isConditionGroup(criteria: unknown): criteria is ConditionGroup { - return ( - _.isArray(criteria) && - criteria.length === 2 && - criteria[0] in ["and", "or"] && - _.isArray(criteria[2]) - ); - } - - private toRelationalValueForUpdate( - columnType: ColumnType, - value: ValueTypes - ): RelationalValue | undefined { - // is null or set null - if (value === null) { - return undefined; - } - - if (columnType === ColumnType.COLUMN_TYPE_INT) { - return { value: { $case: "intValue", intValue: value as number } }; - } - - if (columnType === ColumnType.COLUMN_TYPE_LONG) { - return { value: { $case: "longValue", longValue: value as number } }; - } - - if (columnType === ColumnType.COLUMN_TYPE_FLOAT) { - return { value: { $case: "floatValue", floatValue: value as number } }; - } - - if (columnType === ColumnType.COLUMN_TYPE_DOUBLE) { - return { value: { $case: "doubleValue", doubleValue: value as number } }; - } - - if (columnType === ColumnType.COLUMN_TYPE_DATE) { - return { value: { $case: "dateValue", dateValue: value as string } }; - } - - if (columnType == ColumnType.COLUMN_TYPE_DATETIME) { - return { - value: { $case: "datetimeValue", datetimeValue: value as string }, - }; - } - - if (columnType == ColumnType.COLUMN_TYPE_STRING) { - return { value: { $case: "stringValue", stringValue: value as string } }; - } - - if (columnType == ColumnType.COLUMN_TYPE_BOOLEAN) { - return { - value: { $case: "booleanValue", booleanValue: value as boolean }, - }; - } - - throw new Error(`Unsupported data type ${columnType}`); - } - - private toRelationalValue( - columnType: ColumnType, - values: ValueTypes[] - ): RelationalValue[] { - // is null or set null - if (values.length === 0) { - throw new Error(`Value array should not be empty`); - } - - if (columnType === ColumnType.COLUMN_TYPE_INT) { - return _.map(values, (value) => { - return { value: { $case: "intValue", intValue: value as number } }; - }); - } - - if (columnType === ColumnType.COLUMN_TYPE_LONG) { - return _.map(values, (value) => { - return { value: { $case: "longValue", longValue: value as number } }; - }); - } - - if (columnType === ColumnType.COLUMN_TYPE_FLOAT) { - return _.map(values, (value) => { - return { value: { $case: "floatValue", floatValue: value as number } }; - }); - } - - if (columnType === ColumnType.COLUMN_TYPE_DOUBLE) { - return _.map(values, (value) => { - return { - value: { $case: "doubleValue", doubleValue: value as number }, - }; - }); - } - - if (columnType === ColumnType.COLUMN_TYPE_DATE) { - return _.map(values, (value) => { - return { value: { $case: "dateValue", dateValue: value as string } }; - }); - } - - if (columnType == ColumnType.COLUMN_TYPE_DATETIME) { - return _.map(values, (value) => { - return { - value: { $case: "datetimeValue", datetimeValue: value as string }, - }; - }); - } - - if (columnType == ColumnType.COLUMN_TYPE_STRING) { - return _.map(values, (value) => { - return { - value: { $case: "stringValue", stringValue: value as string }, - }; - }); - } - - if (columnType == ColumnType.COLUMN_TYPE_BOOLEAN) { - return _.map(values, (value) => { - return { - value: { $case: "booleanValue", booleanValue: value as boolean }, - }; - }); - } - - throw new Error(`Unsupported data type ${columnType}`); - } -} diff --git a/clients/client-relational/src/query/index.ts b/clients/client-relational/src/query/index.ts index f0c758a..0ab54ff 100644 --- a/clients/client-relational/src/query/index.ts +++ b/clients/client-relational/src/query/index.ts @@ -1,2 +1,2 @@ export * from "./QueryRunner"; -export * from "./QueryBuilder"; +export * from "./Model";