@malloydata/db-mysql 0.0.196-dev241007230836

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ export { MySQLConnection, MySQLExecutor } from './mysql_connection';
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.MySQLExecutor = exports.MySQLConnection = void 0;
10
+ var mysql_connection_1 = require("./mysql_connection");
11
+ Object.defineProperty(exports, "MySQLConnection", { enumerable: true, get: function () { return mysql_connection_1.MySQLConnection; } });
12
+ Object.defineProperty(exports, "MySQLExecutor", { enumerable: true, get: function () { return mysql_connection_1.MySQLExecutor; } });
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,40 @@
1
+ import { Connection, MalloyQueryData, PersistSQLResults, PooledConnection, QueryRunStats, RunSQLOptions, StreamingConnection, QueryOptionsReader, SQLSourceDef, TableSourceDef } from '@malloydata/malloy';
2
+ import { BaseConnection } from '@malloydata/malloy/connection';
3
+ import * as MYSQL from 'mysql2/promise';
4
+ export interface MySQLConfiguration {
5
+ host?: string;
6
+ port?: number;
7
+ database?: string;
8
+ user?: string;
9
+ password?: string;
10
+ }
11
+ export declare class MySQLExecutor {
12
+ static getConnectionOptionsFromEnv(): MySQLConfiguration;
13
+ }
14
+ export declare class MySQLConnection extends BaseConnection implements Connection, PersistSQLResults {
15
+ private readonly dialect;
16
+ private connection?;
17
+ config: MySQLConfiguration;
18
+ queryOptions: QueryOptionsReader | undefined;
19
+ name: string;
20
+ get dialectName(): string;
21
+ constructor(name: string, config: MySQLConfiguration, queryOptions?: QueryOptionsReader);
22
+ getClient(): Promise<MYSQL.Connection>;
23
+ manifestTemporaryTable(sqlCommand: string): Promise<string>;
24
+ test(): Promise<void>;
25
+ runSQL(sql: string, _options?: RunSQLOptions): Promise<MalloyQueryData>;
26
+ isPool(): this is PooledConnection;
27
+ canPersist(): this is PersistSQLResults;
28
+ canStream(): this is StreamingConnection;
29
+ close(): Promise<void>;
30
+ estimateQueryCost(_sqlCommand: string): Promise<QueryRunStats>;
31
+ fetchTableSchema(tableName: string, tablePath: string): Promise<TableSourceDef>;
32
+ fetchSelectSchema(sqlRef: SQLSourceDef): Promise<SQLSourceDef>;
33
+ private schemaFromResult;
34
+ runRawSQL(sql: string, _options?: RunSQLOptions): Promise<MalloyQueryData>;
35
+ static removeNulls(jsonObj: any): any;
36
+ static removeNullsObject(jsonObj: Record<string, any>): Record<string, any>;
37
+ static removeNullsArray(jsonArray: any[]): any[];
38
+ static checkIsMalloyMetadata(jsonObj: any): any;
39
+ private fillStructDefFromTypeMap;
40
+ }
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || function (mod) {
25
+ if (mod && mod.__esModule) return mod;
26
+ var result = {};
27
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
28
+ __setModuleDefault(result, mod);
29
+ return result;
30
+ };
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.MySQLConnection = exports.MySQLExecutor = void 0;
33
+ const malloy_1 = require("@malloydata/malloy");
34
+ const connection_1 = require("@malloydata/malloy/connection");
35
+ const crypto_1 = require("crypto");
36
+ const MYSQL = __importStar(require("mysql2/promise"));
37
+ class MySQLExecutor {
38
+ static getConnectionOptionsFromEnv() {
39
+ const user = process.env['MYSQL_USER'];
40
+ if (user) {
41
+ const host = process.env['MYSQL_HOST'];
42
+ const port = Number(process.env['MYSQL_PORT']);
43
+ const password = process.env['MYSQL_PASSWORD'];
44
+ const database = process.env['MYSQL_DATABASE'];
45
+ return {
46
+ host,
47
+ port,
48
+ user,
49
+ password,
50
+ database,
51
+ };
52
+ }
53
+ return {};
54
+ }
55
+ }
56
+ exports.MySQLExecutor = MySQLExecutor;
57
+ class MySQLConnection extends connection_1.BaseConnection {
58
+ get dialectName() {
59
+ return this.dialect.name;
60
+ }
61
+ constructor(name, config, queryOptions) {
62
+ super();
63
+ this.dialect = new malloy_1.MySQLDialect();
64
+ this.config = config;
65
+ this.queryOptions = queryOptions;
66
+ this.name = name;
67
+ // TODO: handle when connection fails.
68
+ }
69
+ async getClient() {
70
+ var _a;
71
+ if (!this.connection) {
72
+ this.connection = await MYSQL.createConnection({
73
+ host: this.config.host,
74
+ port: (_a = this.config.port) !== null && _a !== void 0 ? _a : 3306,
75
+ user: this.config.user,
76
+ password: this.config.password,
77
+ database: this.config.database,
78
+ multipleStatements: true,
79
+ decimalNumbers: true,
80
+ });
81
+ this.connection.query(
82
+ // LTNOTE: Need to make the group_concat_max_len configurable.
83
+ "set @@session.time_zone = 'UTC';" +
84
+ // LTNOTE: for nesting this is the max buffer size. Currently set to 10M, have to figure out perf implications.
85
+ 'SET SESSION group_concat_max_len = 10000000;' +
86
+ // Need this to make NULL LAST in order by (ISNULL(exp) can't appear in an ORDER BY without it)
87
+ "SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));");
88
+ }
89
+ return this.connection;
90
+ }
91
+ async manifestTemporaryTable(sqlCommand) {
92
+ const hash = (0, crypto_1.createHash)('md5').update(sqlCommand).digest('hex');
93
+ const tableName = `tt${hash}`;
94
+ const cmd = `CREATE TEMPORARY TABLE IF NOT EXISTS ${tableName} AS (${sqlCommand});`;
95
+ // console.log(cmd);
96
+ await this.runRawSQL(cmd);
97
+ return tableName;
98
+ }
99
+ async test() {
100
+ await this.runRawSQL('SELECT 1');
101
+ }
102
+ runSQL(sql, _options) {
103
+ // TODO: what are options here?
104
+ return this.runRawSQL(sql);
105
+ }
106
+ isPool() {
107
+ return false;
108
+ }
109
+ canPersist() {
110
+ return true;
111
+ }
112
+ canStream() {
113
+ // TODO: implement;
114
+ throw new Error('Method not implemented.2');
115
+ }
116
+ async close() {
117
+ if (this.connection) {
118
+ this.connection.end();
119
+ }
120
+ return undefined;
121
+ }
122
+ estimateQueryCost(_sqlCommand) {
123
+ // TODO: implement;
124
+ throw new Error('Method not implemented.3');
125
+ }
126
+ // get name(): string {
127
+ // return connectionFactory.connectionName;
128
+ // }
129
+ // TODO: make sure this is exercised.
130
+ async fetchTableSchema(tableName, tablePath) {
131
+ const structDef = {
132
+ type: 'table',
133
+ name: tableName,
134
+ tablePath,
135
+ dialect: this.dialectName,
136
+ connection: this.name,
137
+ fields: [],
138
+ };
139
+ const quotedTablePath = tablePath.match(/[:*/]/)
140
+ ? `\`${tablePath}\``
141
+ : tablePath;
142
+ const infoQuery = `DESCRIBE ${quotedTablePath}`;
143
+ const result = await this.runRawSQL(infoQuery);
144
+ await this.schemaFromResult(result, structDef);
145
+ return structDef;
146
+ }
147
+ async fetchSelectSchema(sqlRef) {
148
+ const structDef = {
149
+ ...sqlRef,
150
+ name: sqlRef.name,
151
+ };
152
+ const tempTableName = `tmp${(0, crypto_1.randomUUID)()}`.replace(/-/g, '');
153
+ const client = await this.getClient();
154
+ await client.query(`CREATE TEMPORARY TABLE ${tempTableName} AS (${sqlRef.selectStr});`);
155
+ const [results, _fields] = await client.query(`DESCRIBE ${tempTableName};`);
156
+ // console.log(results); // results contains rows returned by server
157
+ // console.log(fields); // fields contains extra meta data about results, if available
158
+ const rows = results;
159
+ this.schemaFromResult({ rows, totalRows: rows.length }, structDef);
160
+ return structDef;
161
+ }
162
+ async schemaFromResult(result, structDef) {
163
+ const typeMap = {};
164
+ for (const row of result.rows) {
165
+ typeMap[row['Field']] = row['Type'];
166
+ }
167
+ this.fillStructDefFromTypeMap(structDef, typeMap);
168
+ }
169
+ async runRawSQL(sql, _options) {
170
+ // TODO: what are options here?
171
+ const client = await this.getClient();
172
+ try {
173
+ const [results, _fields] = await client.query(sql);
174
+ // console.log(results); // results contains rows returned by server
175
+ // console.log(fields); // fields contains extra meta data about results, if available
176
+ const rows = results;
177
+ return { rows, totalRows: rows.length };
178
+ }
179
+ catch (e) {
180
+ throw new Error('BADNESS');
181
+ }
182
+ }
183
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
184
+ static removeNulls(jsonObj) {
185
+ if (Array.isArray(jsonObj)) {
186
+ return MySQLConnection.removeNullsArray(jsonObj);
187
+ }
188
+ return MySQLConnection.removeNullsObject(jsonObj);
189
+ }
190
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
+ static removeNullsObject(jsonObj) {
192
+ for (const key in jsonObj) {
193
+ if (Array.isArray(jsonObj[key])) {
194
+ jsonObj[key] = MySQLConnection.removeNullsArray(jsonObj[key]);
195
+ }
196
+ else if (typeof jsonObj === 'object') {
197
+ jsonObj[key] = MySQLConnection.removeNullsObject(jsonObj[key]);
198
+ }
199
+ }
200
+ return jsonObj;
201
+ }
202
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
203
+ static removeNullsArray(jsonArray) {
204
+ const metadata = jsonArray
205
+ .filter(MySQLConnection.checkIsMalloyMetadata)
206
+ .shift();
207
+ if (!metadata) {
208
+ return jsonArray;
209
+ }
210
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
211
+ const filteredArray = jsonArray
212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
+ .filter(
214
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
+ (element) => element !== null && !MySQLConnection.checkIsMalloyMetadata(element))
216
+ .map(MySQLConnection.removeNulls);
217
+ if (metadata['limit']) {
218
+ return filteredArray.slice(0, metadata['limit']);
219
+ }
220
+ return filteredArray;
221
+ }
222
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
223
+ static checkIsMalloyMetadata(jsonObj) {
224
+ return (jsonObj !== null &&
225
+ typeof jsonObj === 'object' &&
226
+ jsonObj['_is_malloy_metadata']);
227
+ }
228
+ fillStructDefFromTypeMap(structDef, typeMap) {
229
+ for (const fieldName in typeMap) {
230
+ let mySqlType = typeMap[fieldName].toLocaleLowerCase();
231
+ mySqlType = mySqlType.trim().split('(')[0];
232
+ const malloyType = this.dialect.sqlTypeToMalloyType(mySqlType);
233
+ // no arrays or records exist in mysql
234
+ structDef.fields.push({ ...malloyType, name: fieldName });
235
+ }
236
+ }
237
+ }
238
+ exports.MySQLConnection = MySQLConnection;
239
+ //# sourceMappingURL=mysql_connection.js.map
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@malloydata/db-mysql",
3
+ "version": "0.0.196-dev241007230836",
4
+ "license": "MIT",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "engines": {
8
+ "node": ">=18"
9
+ },
10
+ "homepage": "https://github.com/malloydata/malloy#readme",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/malloydata/malloy"
14
+ },
15
+ "scripts": {
16
+ "lint": "eslint '**/*.ts{,x}'",
17
+ "lint-fix": "eslint '**/*.ts{,x}' --fix",
18
+ "test": "jest --config=../../jest.config.js",
19
+ "build": "tsc --build",
20
+ "clean": "tsc --build --clean",
21
+ "malloyc": "ts-node ../../scripts/malloy-to-json",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "dependencies": {
25
+ "@malloydata/malloy": "^0.0.196-dev241007230836",
26
+ "@types/node": "^22.7.4",
27
+ "fastestsmallesttextencoderdecoder": "^1.0.22",
28
+ "luxon": "^3.5.0",
29
+ "mysql2": "^3.11.3"
30
+ }
31
+ }