UNPKG

imean-cassandra-orm

Version:

cassandra orm

484 lines (477 loc) 16.4 kB
'use strict'; var cassandraDriver = require('cassandra-driver'); var zod = require('zod'); // src/client.ts function createZodSchemaForFields(schema, filter) { const zodSchema = {}; Object.entries(schema).forEach(([key, config]) => { if (filter(config)) { let baseSchema; switch (config.type) { case "text": baseSchema = zod.z.string(); break; case "int": case "bigint": case "varint": baseSchema = zod.z.number().int(); break; case "float": case "double": baseSchema = zod.z.number(); break; case "boolean": baseSchema = zod.z.boolean(); break; case "timestamp": baseSchema = zod.z.date(); break; case "uuid": baseSchema = zod.z.union([zod.z.string(), zod.z.instanceof(cassandraDriver.types.Uuid)]).transform( (val) => val instanceof cassandraDriver.types.Uuid ? val.toString() : val ); break; default: baseSchema = zod.z.any(); } zodSchema[key] = config.flags.optional ? baseSchema.optional() : baseSchema; } }); return zod.z.object(zodSchema); } function createZodSchema(schema) { return createZodSchemaForFields(schema, () => true); } function createPartitionKeyZodSchema(schema) { return createZodSchemaForFields( schema, (config) => config.flags.partitionKey ); } function createClusteringKeyZodSchema(schema) { return createZodSchemaForFields( schema, (config) => !!config.flags.clusteringKey ); } // src/model.ts var Model = class { constructor(client, schema, tableName, keyspace) { this.client = client; this.schema = schema; this.tableName = tableName; this.keyspace = keyspace; this.partitionKeys = Object.entries(schema).filter(([_, config]) => config.flags.partitionKey).map(([key]) => key); this.clusteringKeys = Object.entries(schema).filter(([_, config]) => config.flags.clusteringKey).map(([key]) => key); this.zodSchemas = { entity: createZodSchema(schema), partitionKey: createPartitionKeyZodSchema(schema), clusteringKey: createClusteringKeyZodSchema(schema) }; } tableName; partitionKeys; clusteringKeys; keyspace; zodSchemas; // 获取表结构 CQL getTableSchema() { const columns = Object.entries(this.schema).map(([name, config]) => { const type = config.type; const staticFlag = config.flags.static ? " STATIC" : ""; return `${name} ${type}${staticFlag}`; }).join(",\n "); const primaryKey = this.clusteringKeys.length > 0 ? `((${this.partitionKeys.join(", ")}), ${this.clusteringKeys.join( ", " )})` : `((${this.partitionKeys.join(", ")}))`; const clusteringOrder = this.clusteringKeys.map((key) => { const config = this.schema[key]; if (typeof config.flags.clusteringKey === "object") { return `${key} ${config.flags.clusteringKey.order}`; } return `${key} ASC`; }).join(", "); return `CREATE TABLE IF NOT EXISTS ${this.keyspace}.${this.tableName} ( ${columns}, PRIMARY KEY ${primaryKey} )${clusteringOrder ? ` WITH CLUSTERING ORDER BY (${clusteringOrder})` : ""};`; } // 同步表结构 async syncSchema(force = false) { await this.client.metadata.refreshKeyspace(this.keyspace); const tableInfo = await this.client.metadata.getTable( this.keyspace, this.tableName ); if (!tableInfo) { await this.client.execute(this.getTableSchema(), [], { prepare: true }); return; } const currentColumns = new Map( tableInfo.columns.map((col) => [col.name.toLowerCase(), col]) ); const newFields = Object.entries(this.schema).filter( ([fieldName]) => !currentColumns.has(fieldName.toLowerCase()) ); const changes = []; Object.entries(this.schema).forEach(([fieldName, config]) => { const currentColumn = currentColumns.get(fieldName.toLowerCase()); if (!currentColumn) return; const currentType = this.getCqlTypeFromCode(currentColumn.type.code); const newType = config.type; if (currentType !== newType) { changes.push( `\u5B57\u6BB5 ${fieldName} \u7684\u7C7B\u578B\u4ECE ${currentType} \u53D8\u66F4\u4E3A ${newType}` ); } const isCurrentPartitionKey = tableInfo.partitionKeys.some( (key) => key.name.toLowerCase() === fieldName.toLowerCase() ); if (isCurrentPartitionKey !== config.flags.partitionKey) { changes.push( `\u5B57\u6BB5 ${fieldName} \u7684\u5206\u533A\u952E\u72B6\u6001\u4ECE ${isCurrentPartitionKey} \u53D8\u66F4\u4E3A ${config.flags.partitionKey}` ); } const isCurrentClusteringKey = tableInfo.clusteringKeys.some( (key) => key.name.toLowerCase() === fieldName.toLowerCase() ); const isClusteringKey = !!config.flags.clusteringKey; if (isCurrentClusteringKey !== isClusteringKey) { changes.push( `\u5B57\u6BB5 ${fieldName} \u7684\u805A\u7C7B\u952E\u72B6\u6001\u4ECE ${isCurrentClusteringKey} \u53D8\u66F4\u4E3A ${isClusteringKey}` ); } }); if (newFields.length > 0 && changes.length === 0) { for (const [fieldName, config] of newFields) { await this.client.execute( `ALTER TABLE ${this.keyspace}.${this.tableName} ADD ${fieldName} ${config.type}`, [], { prepare: true } ); } return; } if (changes.length > 0) { if (!force) { throw new Error( `\u68C0\u6D4B\u5230\u8868\u7ED3\u6784\u53D8\u66F4\u9700\u8981\u5220\u9664\u91CD\u5EFA\uFF1A ${changes.join( "\n" )} \u8BF7\u786E\u8BA4\u540E\u4F7F\u7528 force=true \u53C2\u6570\u6267\u884C` ); } await this.client.execute( `DROP TABLE ${this.keyspace}.${this.tableName}`, [], { prepare: true } ); await this.client.execute(this.getTableSchema(), [], { prepare: true }); } } // 根据类型代码获取 CQL 类型 getCqlTypeFromCode(code) { const typeMap = { [cassandraDriver.types.dataTypes.text]: "text", [cassandraDriver.types.dataTypes.int]: "int", [cassandraDriver.types.dataTypes.bigint]: "bigint", [cassandraDriver.types.dataTypes.varint]: "varint", [cassandraDriver.types.dataTypes.boolean]: "boolean", [cassandraDriver.types.dataTypes.timestamp]: "timestamp", [cassandraDriver.types.dataTypes.uuid]: "uuid", [cassandraDriver.types.dataTypes.float]: "float", [cassandraDriver.types.dataTypes.double]: "double", [cassandraDriver.types.dataTypes.blob]: "blob", [cassandraDriver.types.dataTypes.decimal]: "decimal", [cassandraDriver.types.dataTypes.inet]: "inet", [cassandraDriver.types.dataTypes.timeuuid]: "timeuuid", [cassandraDriver.types.dataTypes.duration]: "duration", [cassandraDriver.types.dataTypes.map]: "map", [cassandraDriver.types.dataTypes.set]: "set", [cassandraDriver.types.dataTypes.list]: "list" }; const typeName = typeMap[code]; if (!typeName) { throw new Error(`Unknown type code: ${code}`); } return typeName; } // 将查询结果转换为我们的类型 convertResultToType(result) { const converted = {}; Object.keys(this.schema).forEach((key) => { const lowerKey = key.toLowerCase(); const resultKey = Object.keys(result).find( (k) => k.toLowerCase() === lowerKey ); if (resultKey) { converted[key] = result[resultKey]; } }); return this.zodSchemas.entity.parse(converted); } // 构建查询条件 buildWhereClause(partitionFields, clusteringFields) { const conditions = []; this.partitionKeys.forEach((key) => { const value = partitionFields[key]; if (value !== void 0) { conditions.push(`${key} = ?`); } }); if (clusteringFields) { this.clusteringKeys.forEach((key) => { const value = clusteringFields[key]; if (value !== void 0) { conditions.push(`${key} = ?`); } }); } return conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; } // 构建查询参数 buildQueryParams(partitionFields, clusteringFields) { const params = []; this.partitionKeys.forEach((key) => { const value = partitionFields[key]; if (value !== void 0) { params.push(value); } }); if (clusteringFields) { this.clusteringKeys.forEach((key) => { const value = clusteringFields[key]; if (value !== void 0) { params.push(value); } }); } return params; } // 验证数据 validate(data) { try { this.zodSchemas.entity.parse(data); return true; } catch (error) { return false; } } // 获取验证错误 getValidationErrors(data) { try { this.zodSchemas.entity.parse(data); return null; } catch (error) { return error; } } // 查询方法 async findAll(partitionFields, clusteringFields) { const whereClause = this.buildWhereClause( partitionFields, clusteringFields ); const params = this.buildQueryParams(partitionFields, clusteringFields); const query = `SELECT * FROM ${this.keyspace}.${this.tableName} ${whereClause}`; const result = await this.client.execute(query, params, { prepare: true }); return result.rows.map((row) => this.convertResultToType(row)); } async findOne(partitionFields, clusteringFields) { const whereClause = this.buildWhereClause( partitionFields, clusteringFields ); const params = this.buildQueryParams(partitionFields, clusteringFields); const query = `SELECT * FROM ${this.keyspace}.${this.tableName} ${whereClause} LIMIT 1`; const result = await this.client.execute(query, params, { prepare: true }); return result.rows[0] ? this.convertResultToType(result.rows[0]) : null; } // 添加记录方法 async create(data, options) { const { ttl = 0, ...restOptions } = options || {}; const columns = Object.keys(data).join(", "); const placeholders = Object.keys(data).map(() => "?").join(", "); const params = Object.values(data); const usingTTL = ttl > 0 ? `USING TTL ${ttl}` : ""; const query = `INSERT INTO ${this.keyspace}.${this.tableName} (${columns}) VALUES (${placeholders}) ${usingTTL}`.trim(); await this.client.execute(query, params, { prepare: true, ...restOptions }); } // 更新记录方法 async update(partitionFields, data) { const setClause = Object.keys(data).map((key) => `${key} = ?`).join(", "); const whereClause = this.buildWhereClause(partitionFields); const params = [ ...Object.values(data), ...this.buildQueryParams(partitionFields) ]; const query = `UPDATE ${this.keyspace}.${this.tableName} SET ${setClause} ${whereClause}`; await this.client.execute(query, params, { prepare: true }); } // 删除记录方法 async delete(partitionFields, clusteringFields) { const whereClause = this.buildWhereClause( partitionFields, clusteringFields ); const params = this.buildQueryParams(partitionFields, clusteringFields); const query = `DELETE FROM ${this.keyspace}.${this.tableName} ${whereClause}`; await this.client.execute(query, params, { prepare: true }); } // 优化后的批量查询方法(支持静态列) async findAllOptimized(partitionFields, clusteringFields) { const staticColumns = Object.entries(this.schema).filter(([_, config]) => config.flags.static).map(([name]) => name); const nonStaticColumns = Object.entries(this.schema).filter(([_, config]) => !config.flags.static).map(([name]) => name); const whereClause = this.buildWhereClause( partitionFields, clusteringFields ); const params = this.buildQueryParams(partitionFields, clusteringFields); const [staticResult, nonStaticResult] = await Promise.all([ // 查询静态列 staticColumns.length > 0 ? this.client.execute( `SELECT ${staticColumns.join(", ")} FROM ${this.keyspace}.${this.tableName} ${whereClause} LIMIT 1`, params, { prepare: true } ) : Promise.resolve({ rows: [] }), // 查询非静态列 this.client.execute( `SELECT ${nonStaticColumns.join(", ")} FROM ${this.keyspace}.${this.tableName} ${whereClause}`, params, { prepare: true } ) ]); const staticData = staticResult.rows[0] || {}; return nonStaticResult.rows.map((row) => ({ ...staticData, ...row })); } }; var uuid = () => cassandraDriver.types.Uuid.random().toString(); // src/client.ts var Client2 = class { constructor(cassandraClient) { this.cassandraClient = cassandraClient; } models = /* @__PURE__ */ new Map(); // 使用 keyspace async useKeyspace(keyspace, options) { const checkKeyspaceQuery = ` SELECT keyspace_name FROM system_schema.keyspaces WHERE keyspace_name = ?`; const result = await this.cassandraClient.execute( checkKeyspaceQuery, [keyspace], { prepare: true } ); if (result.rows.length === 0) { let createKeyspaceQuery = `CREATE KEYSPACE IF NOT EXISTS ${keyspace} WITH REPLICATION = { 'class' : '${options.class}'`; if (options.class === "SimpleStrategy") { createKeyspaceQuery += `, 'replication_factor' : ${options.replication_factor || 1}`; } else if (options.class === "NetworkTopologyStrategy" && options.datacenters) { const datacenterConfigs = Object.entries(options.datacenters).map(([dc, rf]) => `'${dc}' : ${rf}`).join(", "); createKeyspaceQuery += `, ${datacenterConfigs}`; } createKeyspaceQuery += " }"; await this.cassandraClient.execute(createKeyspaceQuery, [], { prepare: true }); } } // 创建模型 createModel(schema, tableName, keyspace) { const model = new Model(this.cassandraClient, schema, tableName, keyspace); this.models.set(tableName, model); return model; } // 同步所有表结构 async syncSchema(force = false) { for (const model of this.models.values()) { await model.syncSchema(force); } } // 获取原始 Cassandra 客户端 getRawClient() { return this.cassandraClient; } // 关闭连接 async close() { await this.cassandraClient.shutdown(); } }; // src/field.ts var FieldBuilder = class _FieldBuilder { constructor(type, flags = { partitionKey: false, clusteringKey: false, optional: false, static: false }) { this.type = type; this.flags = flags; } partitionKey() { const newFlags = { ...this.flags, partitionKey: true }; return new _FieldBuilder(this.type, newFlags); } clusteringKey(order) { const newFlags = { ...this.flags, clusteringKey: order ? { order } : true }; return new _FieldBuilder(this.type, newFlags); } optional() { const newFlags = { ...this.flags, optional: true }; return new _FieldBuilder(this.type, newFlags); } static() { const newFlags = { ...this.flags, static: true }; return new _FieldBuilder(this.type, newFlags); } }; var createField = (type) => { return new FieldBuilder(type); }; var Field = { // 文本类型 text: () => createField("text"), uuid: () => createField("uuid"), // 数值类型 int: () => createField("int"), bigint: () => createField("bigint"), varint: () => createField("varint"), decimal: () => createField("decimal"), float: () => createField("float"), double: () => createField("double"), // 布尔类型 boolean: () => createField("boolean"), // 时间类型 timestamp: () => createField("timestamp"), // 二进制类型 blob: () => createField("blob"), // 集合类型 list: () => createField("list"), set: () => createField("set"), map: () => createField("map") }; exports.Client = Client2; exports.Field = Field; exports.FieldBuilder = FieldBuilder; exports.Model = Model; exports.createClusteringKeyZodSchema = createClusteringKeyZodSchema; exports.createPartitionKeyZodSchema = createPartitionKeyZodSchema; exports.createZodSchema = createZodSchema; exports.uuid = uuid;