@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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +13 -0
- package/dist/mysql_connection.d.ts +40 -0
- package/dist/mysql_connection.js +239 -0
- package/package.json +31 -0
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|