From 8ae933f2b8d3efb2de5b89de077c53f2f5289302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EB=B3=91=EA=B4=80?= Date: Mon, 21 Jul 2025 00:19:44 +0900 Subject: [PATCH 1/3] [ADD] typeParser --- packages/main/src/config/SchemaConfig.ts | 38 +++++++++++++++---- .../src/core/DML/implements/InsertBuilder.ts | 10 ++--- .../src/core/DML/implements/SelectBuilder.ts | 17 +-------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/packages/main/src/config/SchemaConfig.ts b/packages/main/src/config/SchemaConfig.ts index 3202854..3f6a2b3 100644 --- a/packages/main/src/config/SchemaConfig.ts +++ b/packages/main/src/config/SchemaConfig.ts @@ -1,3 +1,4 @@ +import { DataTypes, LiteralDataTypes } from "@src/core/DDL/abstracts/BaseFieldBuilder"; import Schema from "@src/core/DDL/implements/Schema" export type MissingSchemaStrategy = 'create' | 'ignore' | 'error' @@ -5,6 +6,8 @@ export type MissingSchemaStrategy = 'create' | 'ignore' | 'error' // 이 인터페이스는 이제 Configs.ts에서 직접 정의되므로 주석 처리하거나 삭제할 수 있습니다. export interface SchemaConfigOptions { onMissingSchema?: MissingSchemaStrategy; + recordDetailType?:boolean, + parseDetailType?:boolean, schemas?: T; } @@ -13,11 +16,38 @@ export type SchemaMap = { } class SchemaConfig{ - missingSchemaStartegy: MissingSchemaStrategy readonly DEFAULT_MISSING_STRATEGY: MissingSchemaStrategy = 'create' + + missingSchemaStartegy: MissingSchemaStrategy + recordDetailType: boolean + parseDetailType: boolean readonly schemaList: T; readonly schemaMap: SchemaMap; + schemaSetted:boolean + + // type parser for spreadsheet values + typeParsers:Record DataTypes> = { + boolean:(value) => value === "true", + date: (value) => new Date(value), + number: (value) => Number(value), + string: (value) => value, + } + + constructor({ + onMissingSchema = this.DEFAULT_MISSING_STRATEGY, + recordDetailType = true, + parseDetailType = true, + schemas = [] as unknown as T, + }: SchemaConfigOptions) { + this.missingSchemaStartegy = onMissingSchema; + this.recordDetailType = recordDetailType; + this.parseDetailType = parseDetailType; + + this.schemaList = schemas; + this.schemaSetted = this.schemaList.length > 0; + this.schemaMap = this.makeSchemaMap(this.schemaList); + } private makeSchemaMap(schemas:T){ const schemaMap = schemas.reduce((schemaDefinition, schema) => { @@ -27,12 +57,6 @@ class SchemaConfig{ }, {} as SchemaMap) return schemaMap } - - constructor(options: SchemaConfigOptions) { - this.missingSchemaStartegy = options.onMissingSchema ?? this.DEFAULT_MISSING_STRATEGY - this.schemaList = (options.schemas ?? []) as T - this.schemaMap = this.makeSchemaMap(this.schemaList) - } } export default SchemaConfig diff --git a/packages/main/src/core/DML/implements/InsertBuilder.ts b/packages/main/src/core/DML/implements/InsertBuilder.ts index dce7d9d..69fcbbf 100644 --- a/packages/main/src/core/DML/implements/InsertBuilder.ts +++ b/packages/main/src/core/DML/implements/InsertBuilder.ts @@ -81,12 +81,12 @@ class SettedInsertBuilder, SelectQueryQueueType>{ const composedRange = this.config.sheet.composeRange(query.sheetName, this.config.sheet.DATA_STARTING_ROW, specifiedColumn) return composedRange }) - console.log("composedRanges",composedRanges) const requestBody = this.makeRequestBody(composedRanges) - console.log("requestBody",requestBody) const response = await this.config.spread.API.spreadsheets.values.batchGetByDataFilter({ spreadsheetId:this.config.spread.ID, @@ -85,19 +83,8 @@ extends WhereableAndQueryStore, SelectQueryQueueType>{ if (!(queriedFrom in this.config.schema.schemaMap)) return sheetValue const currentSchema = this.config.schema.schemaMap[queriedFrom as keyof SchemaMap] - const typeConverters:((value:string)=>DataTypes)[] = currentSchema.orderedColumns.map((column) => { - const type = currentSchema.fields[column].dataType - switch (type){ - case "boolean": - return (value:string) => value.toLowerCase() === "true" - case "date": - return (value:string) => new Date(value) - case "number": - return (value:string) => Number(value) - default: - return (value:string) => value - } - }) + // orderColumns 를 기준으로 typeParser메서드들이 indexing된 배열을 + const typeConverters:((value:string)=>DataTypes)[] = currentSchema.orderedColumns.map((column) => this.config.schema.typeParsers[currentSchema.fields[column].dataType]) const result = sheetValue.map((row) => row.map((value,idx) => typeConverters[idx](value))) return result From cd45e2c6144d8a184f594fe65b0c267b8eef5498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EB=B3=91=EA=B4=80?= Date: Mon, 21 Jul 2025 23:34:19 +0900 Subject: [PATCH 2/3] [FIX] setTypedCell range --- packages/main/src/config/SpreadConfig.ts | 10 +++--- packages/main/src/core/DDL/SchemaManager.ts | 9 ++--- .../src/core/DML/implements/InsertBuilder.ts | 35 +++++++++++-------- packages/main/src/generators/SheetQueries.ts | 16 ++++----- packages/test/index.ts | 6 ++-- 5 files changed, 42 insertions(+), 34 deletions(-) diff --git a/packages/main/src/config/SpreadConfig.ts b/packages/main/src/config/SpreadConfig.ts index 6f8e513..d11758f 100644 --- a/packages/main/src/config/SpreadConfig.ts +++ b/packages/main/src/config/SpreadConfig.ts @@ -72,10 +72,12 @@ class SpreadConfig{ } async batchUpdateQuery(requests: sheets_v4.Schema$Request[], spreadsheetId?: string,) { - const response = await this.API.spreadsheets.batchUpdate({ - spreadsheetId:spreadsheetId ?? this.ID, - requestBody: { requests } - }); + const request = { + spreadsheetId:spreadsheetId ?? this.ID, + requestBody: { requests } + } + console.log(JSON.stringify(request)) + const response = await this.API.spreadsheets.batchUpdate(request); if (response.status !== 200) throw new Error("Batch failed"); return response.data.replies; } diff --git a/packages/main/src/core/DDL/SchemaManager.ts b/packages/main/src/core/DDL/SchemaManager.ts index 0326d61..74c7f84 100644 --- a/packages/main/src/core/DDL/SchemaManager.ts +++ b/packages/main/src/core/DDL/SchemaManager.ts @@ -149,16 +149,17 @@ class SchemaManager { if (dataType !== "number" && dataType !== "date") continue - const startColumnIndex = this.config.sheet.columnToNumber(this.config.sheet.DEFAULT_RECORDING_START_COLUMN) + idx - const request = SheetQueries.repeatTypedCell(sheetId, dataType, { - startRowIndex: this.config.sheet.DATA_STARTING_ROW, + const startColumnIndex = this.config.sheet.columnToNumber(this.config.sheet.DEFAULT_RECORDING_START_COLUMN) - 1 + idx + const request = SheetQueries.setNumberTypedCell(dataType, { + sheetId, + startRowIndex: this.config.sheet.DATA_STARTING_ROW - 1, startColumnIndex, endColumnIndex: startColumnIndex + 1 }) setTypedColumnRequests.push(request) } } - this.config.spread.batchUpdateQuery(setTypedColumnRequests) + await this.config.spread.batchUpdateQuery(setTypedColumnRequests) console.log("set type to column successfully") return result diff --git a/packages/main/src/core/DML/implements/InsertBuilder.ts b/packages/main/src/core/DML/implements/InsertBuilder.ts index 69fcbbf..a29d41a 100644 --- a/packages/main/src/core/DML/implements/InsertBuilder.ts +++ b/packages/main/src/core/DML/implements/InsertBuilder.ts @@ -96,21 +96,28 @@ class SettedInsertBuilder= 61) { - return serial + 1; + private jsDateToSheetsSerial(jsDate:Date) { + // Google Sheets의 기준 날짜 (1899년 12월 30일) + // JavaScript Date 객체는 월을 0부터 시작하므로 11은 12월을 의미합니다. + const sheetsEpoch = new Date(1899, 11, 30, 0, 0, 0); + + // JavaScript Date 객체와 Sheets 기준 날짜 간의 밀리초 차이 계산 + const diffMillis = jsDate.getTime() - sheetsEpoch.getTime(); + + // 1일의 밀리초 (24시간 * 60분 * 60초 * 1000밀리초) + const millisPerDay = 24 * 60 * 60 * 1000; + + // 일수로 변환 + let sheetsSerial = diffMillis / millisPerDay; + + // Google Sheets (및 Excel)의 1900년 윤년 버그 보정 + // 1900년 2월 29일은 실제로는 존재하지 않았지만, Sheets는 이를 날짜로 계산합니다. + // 따라서 1900년 3월 1일 이후의 날짜에는 1을 더해줘야 합니다. + if (jsDate.getFullYear() > 1900 || (jsDate.getFullYear() === 1900 && jsDate.getMonth() > 1)) { + sheetsSerial += 1; } - return serial; + + return sheetsSerial; } } \ No newline at end of file diff --git a/packages/main/src/generators/SheetQueries.ts b/packages/main/src/generators/SheetQueries.ts index d74df23..3eaa846 100644 --- a/packages/main/src/generators/SheetQueries.ts +++ b/packages/main/src/generators/SheetQueries.ts @@ -20,30 +20,28 @@ export class SheetQueries { } } - static repeatTypedCell( - sheetId:number, + static setNumberTypedCell( type:"number" | "date", - range:Omit = {endRowIndex:1000000}) + range:sheets_v4.Schema$GridRange) :sheets_v4.Schema$Request{ const numberFormat = { "number":{ - type:"NUMBER" + type:"NUMBER", }, "date":{ type:"DATE_TIME", - pattern: "yyyy-mm-dd hh:mm:ss" } } - return { repeatCell:{ range:{ - sheetId, - ...range, + startRowIndex:2, + endRowIndex:1000000, + ...range }, cell:{ userEnteredFormat: { - numberFormat:numberFormat[type] + numberFormat:numberFormat[type], } }, fields:"userEnteredFormat.numberFormat" diff --git a/packages/test/index.ts b/packages/test/index.ts index f2849fa..2cbf936 100644 --- a/packages/test/index.ts +++ b/packages/test/index.ts @@ -36,14 +36,14 @@ const spreadsheetClient = createSpreadsheetClient({ }) // spreadsheetClient.configs.schema.schemaMap.cars await spreadsheetClient.schemaManager.sync({mode:"force"}) -await spreadsheetClient.queryBuilder.insert(["volvo", 1960]).into("cars").execute() +// await spreadsheetClient.queryBuilder.insert(["volvo", 1960]).into("cars").execute() // await spreadsheetClient.queryBuilder.insert(["volvo",1960]).into("cars").execute() // await spreadsheetClient.queryBuilder.delete().from("cars").where((data) => data[2] === "1960").execute() // index0 = index // await spreadsheetClient.queryBuilder.update(["hyundai", 2000]).from("cars").execute() // await spreadsheetClient.queryBuilder.insert(["volvo",1960]).into("cars").and(["hyundai", 2020]).into("cars").execute() // // await spreadsheetClient.queryBuilder.insert(["volve", 1960]).into("cars").and() -const result = await spreadsheetClient.queryBuilder.select().from("cars").and().from("user").execute({detail:false}) -console.log(result) +// const result = await spreadsheetClient.queryBuilder.select().from("cars").and().from("user").execute({detail:false}) +// console.log(result) // await spreadsheetClient.queryBuilder.update() From 88b682a8bac9f4833c8b210939e74038112087c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EB=B3=91=EA=B4=80?= Date: Mon, 21 Jul 2025 23:58:21 +0900 Subject: [PATCH 3/3] [CHECKPOINT] for fillInsertValues logic --- packages/main/src/config/SpreadConfig.ts | 14 ++++++- .../src/core/DML/implements/InsertBuilder.ts | 37 ++++--------------- packages/test/index.ts | 5 +-- 3 files changed, 22 insertions(+), 34 deletions(-) diff --git a/packages/main/src/config/SpreadConfig.ts b/packages/main/src/config/SpreadConfig.ts index d11758f..48a63a7 100644 --- a/packages/main/src/config/SpreadConfig.ts +++ b/packages/main/src/config/SpreadConfig.ts @@ -25,6 +25,18 @@ class SpreadConfig{ return false } + // Google Sheets 시리얼 번호로 변환하는 헬퍼 함수 + static jsDateToSheetsSerial(jsDate:Date) { + const sheetsEpoch = new Date(1899, 11, 30, 0, 0, 0); + const diffMillis = jsDate.getTime() - sheetsEpoch.getTime(); + const millisPerDay = 24 * 60 * 60 * 1000; + let sheetsSerial = diffMillis / millisPerDay; + if (jsDate.getFullYear() > 1900 || (jsDate.getFullYear() === 1900 && jsDate.getMonth() > 1)) { + sheetsSerial += 1; + } + + return sheetsSerial; + } /** * instance properties @@ -76,12 +88,12 @@ class SpreadConfig{ spreadsheetId:spreadsheetId ?? this.ID, requestBody: { requests } } - console.log(JSON.stringify(request)) const response = await this.API.spreadsheets.batchUpdate(request); if (response.status !== 200) throw new Error("Batch failed"); return response.data.replies; } + private checkFormat(options:SpreadConfigOptions){ if (!this.isValidEmail(options.email)){ throw Error("Invalid email format") diff --git a/packages/main/src/core/DML/implements/InsertBuilder.ts b/packages/main/src/core/DML/implements/InsertBuilder.ts index a29d41a..cdb1aea 100644 --- a/packages/main/src/core/DML/implements/InsertBuilder.ts +++ b/packages/main/src/core/DML/implements/InsertBuilder.ts @@ -4,6 +4,7 @@ import Schema from "@src/core/DDL/implements/Schema"; import AndAbleQueryStore from "../abstracts/mixins/AndAbleQueryStore"; import QueryStore, { BasicQueryQueueType } from "../abstracts/QueryStore"; import { SchemaMap } from "@src/config/SchemaConfig"; +import SpreadConfig from "@src/config/SpreadConfig"; interface InsertQueryQueueType extends BasicQueryQueueType{ insertValues:DataTypes[] @@ -75,18 +76,17 @@ class SettedInsertBuilder { const newRow = [...row]; // 원본 행 복사 for (const fieldName in schema.fields) { const field = schema.fields[fieldName]; - // if (field.dataType === 'date' && field.timestampAtCreated) { - // const columnIndex = field.columnOrder - 1; // columnOrder는 1부터 시작, 배열 인덱스는 0부터 시작 - - // const now = new Date(); - // newRow[columnIndex] = this.dateToGoogleSheetsSerial(now); // not working now - // } + if (field.dataType === 'date' && field.timestampAtCreated) { + const columnIndex = field.columnOrder - 1; // columnOrder는 1부터 시작, 배열 인덱스는 0부터 시작 + const now = new Date(); + newRow[columnIndex] = SpreadConfig.jsDateToSheetsSerial(now); // not working now + } } return newRow; }); @@ -95,29 +95,6 @@ class SettedInsertBuilder 1900 || (jsDate.getFullYear() === 1900 && jsDate.getMonth() > 1)) { - sheetsSerial += 1; - } - - return sheetsSerial; - } } \ No newline at end of file diff --git a/packages/test/index.ts b/packages/test/index.ts index 2cbf936..fb14547 100644 --- a/packages/test/index.ts +++ b/packages/test/index.ts @@ -1,6 +1,5 @@ import createSpreadsheetClient, { Credentials, defineTable, fieldBuilder } from "spreadsheet-orm" import credentials from "./security/credentials.json" - const connectionParameters:Credentials = credentials const userSchemaFields = { @@ -36,8 +35,8 @@ const spreadsheetClient = createSpreadsheetClient({ }) // spreadsheetClient.configs.schema.schemaMap.cars await spreadsheetClient.schemaManager.sync({mode:"force"}) -// await spreadsheetClient.queryBuilder.insert(["volvo", 1960]).into("cars").execute() -// await spreadsheetClient.queryBuilder.insert(["volvo",1960]).into("cars").execute() + +await spreadsheetClient.queryBuilder.insert(["volvo",1960]).into("cars").execute() // await spreadsheetClient.queryBuilder.delete().from("cars").where((data) => data[2] === "1960").execute() // index0 = index // await spreadsheetClient.queryBuilder.update(["hyundai", 2000]).from("cars").execute() // await spreadsheetClient.queryBuilder.insert(["volvo",1960]).into("cars").and(["hyundai", 2020]).into("cars").execute()