diff --git a/core/internal/psql/insert.go b/core/internal/psql/insert.go index 5acc3069..1838e3bc 100644 --- a/core/internal/psql/insert.go +++ b/core/internal/psql/insert.go @@ -40,9 +40,6 @@ func (c *compilerContext) renderInsertStmt(m qcode.Mutate, embedded bool) { c.renderValues(m, false) if !embedded { - c.w.WriteString(` RETURNING `) - c.table(m.Ti.Schema, m.Ti.Name, false) - c.w.WriteString(`.*)`) - + c.renderReturning(m) } } diff --git a/core/internal/psql/mutate.go b/core/internal/psql/mutate.go index e268ed62..6b13374a 100644 --- a/core/internal/psql/mutate.go +++ b/core/internal/psql/mutate.go @@ -318,11 +318,12 @@ func (c *compilerContext) renderUpsert() { c.w.WriteString(` WHERE `) c.renderExp(m.Ti, sel.Where.Exp, false) - c.w.WriteString(` RETURNING *) `) + c.renderReturning(m) } func (c *compilerContext) renderDelete() { sel := c.qc.Selects[0] + m := c.qc.Mutates[0] c.w.WriteString(`WITH `) c.quoted(sel.Table) @@ -332,9 +333,7 @@ func (c *compilerContext) renderDelete() { c.w.WriteString(` WHERE `) c.renderExp(sel.Ti, sel.Where.Exp, false) - c.w.WriteString(` RETURNING `) - c.table(sel.Ti.Schema, sel.Ti.Name, false) - c.w.WriteString(`.*) `) + c.renderReturning(m) } func (c *compilerContext) renderOneToManyConnectStmt(m qcode.Mutate) { @@ -387,10 +386,7 @@ func (c *compilerContext) renderOneToOneConnectStmt(m qcode.Mutate) { c.w.WriteString(` WHERE `) c.renderExpPath(m.Ti, m.Where.Exp, false, m.Path) - - c.w.WriteString(` RETURNING `) - c.table(m.Ti.Schema, m.Ti.Name, false) - c.w.WriteString(`.*)`) + c.renderReturning(m) } func (c *compilerContext) renderOneToManyDisconnectStmt(m qcode.Mutate) { @@ -461,10 +457,7 @@ func (c *compilerContext) renderOneToOneDisconnectStmt(m qcode.Mutate) { c.renderExpPath(m.Ti, m.Where.Exp, false, m.Path) } c.w.WriteString(`)`) - - c.w.WriteString(` RETURNING `) - c.table(m.Ti.Schema, m.Ti.Name, false) - c.w.WriteString(`.*)`) + c.renderReturning(m) } func (c *compilerContext) renderOneToManyModifiers(m qcode.Mutate) { @@ -553,6 +546,12 @@ func (c *compilerContext) renderMutateToRecordSet(m qcode.Mutate, n int) { c.w.WriteString(`)`) } +func (c *compilerContext) renderReturning(m qcode.Mutate) { + c.w.WriteString(` RETURNING `) + c.table(m.Ti.Schema, m.Ti.Name, false) + c.w.WriteString(`.*)`) +} + func (c *compilerContext) renderComma(i int) int { if i != 0 { c.w.WriteString(`, `) diff --git a/core/internal/psql/update.go b/core/internal/psql/update.go index 92d68bfc..e7a0fb71 100644 --- a/core/internal/psql/update.go +++ b/core/internal/psql/update.go @@ -73,8 +73,5 @@ func (c *compilerContext) renderUpdateStmt(m qcode.Mutate) { } c.w.WriteString(`)`) } - - c.w.WriteString(` RETURNING `) - c.table(m.Ti.Schema, m.Ti.Name, false) - c.w.WriteString(`.*)`) + c.renderReturning(m) } diff --git a/core/internal/sdata/sql/mysql_functions.sql b/core/internal/sdata/sql/mysql_functions.sql index e6eb761f..f0b592f1 100644 --- a/core/internal/sdata/sql/mysql_functions.sql +++ b/core/internal/sdata/sql/mysql_functions.sql @@ -6,7 +6,7 @@ SELECT p.ordinal_position as param_id, COALESCE(p.parameter_name, '') as param_name, p.data_type as param_type, - p.parameter_mode as param_kind + COALESCE(p.parameter_mode, '') as param_kind FROM information_schema.routines r RIGHT JOIN diff --git a/core/internal/sdata/sql/postgres_functions.sql b/core/internal/sdata/sql/postgres_functions.sql index dc621692..062376ce 100644 --- a/core/internal/sdata/sql/postgres_functions.sql +++ b/core/internal/sdata/sql/postgres_functions.sql @@ -6,7 +6,7 @@ SELECT p.ordinal_position as param_id, COALESCE(p.parameter_name, '') as param_name, p.data_type as param_type, - p.parameter_mode as param_kind + COALESCE(p.parameter_mode, '') as param_kind FROM information_schema.routines r RIGHT JOIN diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index cb01d9af..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,40 +0,0 @@ -version: "3.4" -services: - db: - image: postgres:12 - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - ports: - - "5432:5432" - - # payments: - # image: rivancic/json-server-faker:latest - # volumes: - # - ./examples/webshop/config/:/ - # - ./examples/webshop/config/file.js:/data/file.js - # ports: - # - "4000:80" - - api: - build: - context: . - target: go-build - environment: - GO_ENV: "development" - GJ_DATABASE_HOST: db - GJ_DATABASE_USER: postgres - GJ_DATABASE_PASSWORD: postgres - CGO_ENABLED: 0 - PORT: 8080 - ports: - - "8080:8080" - volumes: - - .:/home/nonroot - - ./examples/webshop/config:/home/nonroot/config - working_dir: /home/nonroot - command: wtc - depends_on: - - db - # - rails_app - # - redis diff --git a/examples/nodejs/mysql.js b/examples/nodejs/mysql.js new file mode 100644 index 00000000..3450745f --- /dev/null +++ b/examples/nodejs/mysql.js @@ -0,0 +1,49 @@ +import graphjin from "graphjin"; +import express from "express"; +import http from "http"; +import mysql from "mysql2" + +const pool = mysql.createPool({ + host: 'localhost', + port: '/tmp/mysql.sock', + user: 'root', + database: 'db', + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0 +}); + +const db = pool.promise(); +const app = express(); +const server = http.createServer(app); + +// config can either be a filename (eg. `dev.yml`) or an object +const config = { + production: false, + db_type: "mysql", + disable_allow_list: true +}; + +const gj = await graphjin("./config", config, db); + + +const res1 = await gj.subscribe( + "subscription getUpdatedUser { users(id: $userID) { id email } }", + null, + { userID: 2 }) + +res1.data(function(res) { + console.log(">", res.data()) +}) + +app.get('/', async function(req, resp) { + const res2 = await gj.query( + "query getUser { users(id: $id) { id email } }", + { id: 1 }, + { userID: 1 }) + + resp.send(res2.data()); +}); + +server.listen(3000); +console.log('Express server started on port %s', server.address().port); diff --git a/examples/nodejs/package-lock.json b/examples/nodejs/package-lock.json index 1b83e1e3..714a73af 100644 --- a/examples/nodejs/package-lock.json +++ b/examples/nodejs/package-lock.json @@ -1,23 +1,24 @@ { - "name": "nodejs-express-example", + "name": "nodejs-examples", "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "nodejs-express-example", + "name": "nodejs-examples", "license": "Apache-2.0", "dependencies": { "express": "^4.18.2", "graphjin": "file:../..", - "pg": "^8.8.0" + "mysql2": "^2.3.3", + "pg-pool": "^3.5.2" }, "devDependencies": { "path": "^0.12.7" } }, "../..": { - "version": "2.0.6", + "version": "2.0.12", "hasInstallScript": true, "license": "Apache-2.0", "devDependencies": { @@ -71,6 +72,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "peer": true, "engines": { "node": ">=4" } @@ -135,6 +137,14 @@ "ms": "2.0.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -257,6 +267,14 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/get-intrinsic": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", @@ -335,6 +353,27 @@ "node": ">= 0.10" } }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -391,6 +430,60 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/mysql2": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", + "integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==", + "dependencies": { + "denque": "^2.0.1", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^4.0.0", + "lru-cache": "^6.0.0", + "named-placeholders": "^1.1.2", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz", + "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", + "dependencies": { + "lru-cache": "^4.1.3" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/named-placeholders/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -421,7 +514,8 @@ "node_modules/packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", + "peer": true }, "node_modules/parseurl": { "version": "1.3.3", @@ -450,6 +544,7 @@ "version": "8.8.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "peer": true, "dependencies": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", @@ -474,12 +569,14 @@ "node_modules/pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==", + "peer": true }, "node_modules/pg-int8": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "peer": true, "engines": { "node": ">=4.0.0" } @@ -495,12 +592,14 @@ "node_modules/pg-protocol": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", - "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==", + "peer": true }, "node_modules/pg-types": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "peer": true, "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", @@ -516,6 +615,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "peer": true, "dependencies": { "split2": "^4.1.0" } @@ -524,6 +624,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "peer": true, "engines": { "node": ">=4" } @@ -532,6 +633,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -540,6 +642,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -548,6 +651,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "peer": true, "dependencies": { "xtend": "^4.0.0" }, @@ -576,6 +680,11 @@ "node": ">= 0.10" } }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -664,6 +773,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", @@ -700,10 +814,19 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "peer": true, "engines": { "node": ">= 10.x" } }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -775,9 +898,15 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "peer": true, "engines": { "node": ">=0.4" } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } }, "dependencies": { @@ -817,7 +946,8 @@ "buffer-writer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "peer": true }, "bytes": { "version": "3.1.2", @@ -864,6 +994,11 @@ "ms": "2.0.0" } }, + "denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -961,6 +1096,14 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "requires": { + "is-property": "^1.0.2" + } + }, "get-intrinsic": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", @@ -1020,6 +1163,24 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1058,6 +1219,55 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "mysql2": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", + "integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==", + "requires": { + "denque": "^2.0.1", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^4.0.0", + "lru-cache": "^6.0.0", + "named-placeholders": "^1.1.2", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "named-placeholders": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz", + "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", + "requires": { + "lru-cache": "^4.1.3" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + } + } + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1079,7 +1289,8 @@ "packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", + "peer": true }, "parseurl": { "version": "1.3.3", @@ -1105,6 +1316,7 @@ "version": "8.8.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==", + "peer": true, "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", @@ -1118,12 +1330,14 @@ "pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==", + "peer": true }, "pg-int8": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "peer": true }, "pg-pool": { "version": "3.5.2", @@ -1134,12 +1348,14 @@ "pg-protocol": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", - "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==", + "peer": true }, "pg-types": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "peer": true, "requires": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", @@ -1152,6 +1368,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "peer": true, "requires": { "split2": "^4.1.0" } @@ -1159,22 +1376,26 @@ "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "peer": true }, "postgres-bytea": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "peer": true }, "postgres-date": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "peer": true }, "postgres-interval": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "peer": true, "requires": { "xtend": "^4.0.0" } @@ -1194,6 +1415,11 @@ "ipaddr.js": "1.9.1" } }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, "qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -1255,6 +1481,11 @@ } } }, + "seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", @@ -1284,7 +1515,13 @@ "split2": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "peer": true + }, + "sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==" }, "statuses": { "version": "2.0.1", @@ -1340,7 +1577,13 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "peer": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/examples/nodejs/package.json b/examples/nodejs/package.json index 4516c2e5..b11ec76c 100644 --- a/examples/nodejs/package.json +++ b/examples/nodejs/package.json @@ -1,18 +1,16 @@ { - "name": "nodejs-express-example", + "name": "nodejs-examples", "type": "module", "private": true, "description": "Example app for using Graphjin with an NodeJS Express app", "author": "Vikram ", "license": "Apache-2.0", - "scripts": { - "start": "NODE_ENV=production node index.js", - "dev": "node index.js" - }, + "scripts": {}, "dependencies": { "express": "^4.18.2", "graphjin": "file:../..", - "pg": "^8.8.0" + "mysql2": "^2.3.3", + "pg-pool": "^3.5.2" }, "devDependencies": { "path": "^0.12.7" diff --git a/examples/nodejs/index.js b/examples/nodejs/postgres.js similarity index 86% rename from examples/nodejs/index.js rename to examples/nodejs/postgres.js index 717f8124..572c99d7 100644 --- a/examples/nodejs/index.js +++ b/examples/nodejs/postgres.js @@ -1,10 +1,10 @@ import graphjin from "graphjin"; import express from "express"; import http from "http"; -import pg from "pg" -const { Client } = pg -const db = new Client({ +import PgPool from "pg-pool" + +const db = new PgPool({ host: 'localhost', port: 5432, user: 'postgres', @@ -12,9 +12,7 @@ const db = new Client({ database: "42papers-development" }) -await db.connect() - -// config can either be a file (eg. `dev.yml`) or an object +// config can either be a filename (eg. `dev.yml`) or an object // const config = { production: true, default_limit: 50 }; const app = express(); diff --git a/go.mod b/go.mod index 8a8f4fc8..0cfeba88 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3 github.com/avast/retry-go v3.0.0+incompatible github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 - github.com/brianvoe/gofakeit/v6 v6.19.0 + github.com/brianvoe/gofakeit/v6 v6.20.1 github.com/chirino/graphql v0.0.0-20220710191258-f420c1213e22 github.com/dop251/goja v0.0.0-20221118162653-d4bf6fde1b86 github.com/fsnotify/fsnotify v1.6.0 diff --git a/go.sum b/go.sum index 547ea38b..fbde53e0 100644 --- a/go.sum +++ b/go.sum @@ -278,6 +278,8 @@ github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822 h1:hjXJeBcAMS1 github.com/bradfitz/gomemcache v0.0.0-20221031212613-62deef7fc822/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/brianvoe/gofakeit/v6 v6.19.0 h1:g+yJ+meWVEsAmR+bV4mNM/eXI0N+0pZ3D+Mi+G5+YQo= github.com/brianvoe/gofakeit/v6 v6.19.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= +github.com/brianvoe/gofakeit/v6 v6.20.1 h1:8ihJ60OvPnPJ2W6wZR7M+TTeaZ9bml0z6oy4gvyJ/ek= +github.com/brianvoe/gofakeit/v6 v6.20.1/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= diff --git a/package.json b/package.json index d5b344a6..c8888424 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graphjin", - "version": "2.0.12", + "version": "2.0.13", "description": "GraphJin - Build APIs in 5 minutes with GraphQL", "type": "module", "main": "./wasm/js/graphjin.js", diff --git a/wasm/db.go b/wasm/db.go index a6aa5c35..e944ff14 100644 --- a/wasm/db.go +++ b/wasm/db.go @@ -3,13 +3,10 @@ package main import ( - "context" "database/sql/driver" - "errors" "fmt" "hash/fnv" "io" - "strconv" "sync" "syscall/js" ) @@ -20,219 +17,6 @@ var rowsPool = sync.Pool{ }, } -type JSPostgresDB struct { -} - -func (d *JSPostgresDB) Open(name string) (driver.Conn, error) { - return nil, errors.New("use openwithclient") -} - -type Connector struct { - client js.Value - pool sync.Pool -} - -func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) { - return &Conn{client: c.client}, nil -} - -func (c *Connector) Driver() driver.Driver { - return &JSPostgresDB{} -} - -func NewJSPostgresDBConn(client js.Value) driver.Connector { - return &Connector{client: client} -} - -type Conn struct { - client js.Value -} - -func (c *Conn) Prepare(query string) (driver.Stmt, error) { - st := &Stmt{ - client: c.client, - key: strconv.FormatUint(uint64(hash(query)), 10), - query: query, - numInput: -1, - } - return st, nil -} - -func (c *Conn) QueryContext(ctx context.Context, query string, nargs []driver.NamedValue) (driver.Rows, error) { - args := make([]driver.Value, len(nargs)) - for _, v := range nargs { - args[(v.Ordinal - 1)] = v.Value - } - - st, err := c.Prepare(query) - if err != nil { - return nil, err - } - return st.Query(args) -} - -func (c *Conn) Close() error { - return nil -} - -func (c *Conn) Begin() (driver.Tx, error) { - return &Tx{client: c.client}, nil -} - -type Tx struct { - client js.Value -} - -func (t *Tx) Begin() (driver.Tx, error) { - return t, nil -} - -func (t *Tx) Commit() error { - return nil -} - -func (t *Tx) Rollback() error { - return nil -} - -type Stmt struct { - client js.Value - key string - query string - numInput int -} - -func (s *Stmt) Close() error { - await(s.client.Call("end")) - return nil -} - -func (s *Stmt) NumInput() int { - return s.numInput -} - -func (s *Stmt) Exec(args []driver.Value) (driver.Result, error) { - res := &Result{lastInsertId: -1} - v, err := s.queryExec(args) - if err != nil { - return res, err - } - res.rowsAffected = int64(v.Get("rowCount").Int()) - return res, nil -} - -func (s *Stmt) Query(args []driver.Value) (driver.Rows, error) { - v, err := s.queryExec(args) - if err != nil { - return nil, err - } - - cols := v.Get("fields") - rows := v.Get("rows") - - cl := cols.Length() - rl := rows.Length() - - ret := rowsPool.Get().(*Rows) - *ret = zeroRows // wipe clean for reuse - - ret.useArray = (cl == 1 && rl == 1 && rows.Index(0).Length() == 1) - - if ret.useArray { - ret.colsA[0] = cols.Index(0).Get("name").String() - ret.rowsA[0][0] = colVal(rows.Index(0).Index(0)) - return ret, nil - } - - ret.cols = make([]string, cols.Length()) - ret.rows = make([][]interface{}, rows.Length()) - - rowLen := -1 - if rl != 0 && rows.Index(0).Length() != 0 { - rowLen = rows.Index(0).Length() - } - - for i := 0; i < len(ret.cols); i++ { - name := cols.Index(i).Get("name").String() - ret.cols[i] = name - } - - for i := 0; i < len(ret.rows); i++ { - row := rows.Index(i) - ret.rows[i] = make([]interface{}, rowLen) - - for j := 0; j < rowLen; j++ { - ret.rows[i][j] = colVal(row.Index(j)) - } - } - return ret, nil -} - -func colVal(col js.Value) interface{} { - switch col.Type() { - case js.TypeBoolean: - return col.Bool() - case js.TypeNumber: - return col.Int() - case js.TypeString: - return []byte(col.String()) - default: - return nil - } -} - -func getTypeParser() js.Func { - return js.FuncOf(func(this js.Value, args []js.Value) interface{} { - return js.FuncOf(func(this js.Value, args []js.Value) interface{} { - return args[0] - }) - }) -} - -func (s *Stmt) queryExec(args []driver.Value) (js.Value, error) { - m := map[string]interface{}{ - "rowMode": "array", - "name": s.key, - "text": s.query, - "types": map[string]interface{}{ - "getTypeParser": getTypeParser(), - }, - } - - vals := make([]interface{}, len(args)) - for i, a := range args { - switch v := a.(type) { - case []byte: - vals[i] = js.ValueOf(string(v)) - default: - vals[i] = js.ValueOf(v) - } - } - m["values"] = vals - - res, rej := await(s.client.Call("query", m)) - - if len(rej) != 0 { - err := errors.New(rej[0].Get("message").String()) - return js.Null(), err - } - - return res[0], nil -} - -type Result struct { - lastInsertId int64 - rowsAffected int64 -} - -func (r *Result) LastInsertId() (int64, error) { - return r.lastInsertId, nil -} - -func (r *Result) RowsAffected() (int64, error) { - return r.rowsAffected, nil -} - type Rows struct { cols []string rows [][]interface{} @@ -281,6 +65,32 @@ func (r *Rows) Next(dest []driver.Value) error { return nil } +type Result struct { + lastInsertId int64 + rowsAffected int64 +} + +func (r *Result) LastInsertId() (int64, error) { + return r.lastInsertId, nil +} + +func (r *Result) RowsAffected() (int64, error) { + return r.rowsAffected, nil +} + +func colVal(col js.Value) interface{} { + switch col.Type() { + case js.TypeBoolean: + return col.Bool() + case js.TypeNumber: + return col.Int() + case js.TypeString: + return []byte(col.String()) + default: + return nil + } +} + func hash(s string) uint32 { h := fnv.New32a() h.Write([]byte(s)) diff --git a/wasm/graphjin.wasm b/wasm/graphjin.wasm index c0416be8..7b362819 100755 Binary files a/wasm/graphjin.wasm and b/wasm/graphjin.wasm differ diff --git a/wasm/main.go b/wasm/main.go index 0216a844..a9822db9 100644 --- a/wasm/main.go +++ b/wasm/main.go @@ -13,7 +13,7 @@ import ( ) func main() { - sql.Register("postgres", &JSPostgresDB{}) + sql.Register("postgres", &PgDB{}) js.Global().Set("createGraphJin", graphjinFunc()) <-make(chan bool) } @@ -52,18 +52,30 @@ func graphjinFunc() js.Func { return toJSError(err) } - conf := cov.Get("value").String() - confIsFile := cov.Get("isFile").Bool() - - db := sql.OpenDB(NewJSPostgresDBConn(dbv)) fs := NewJSFSWithBase(fsv, cpv.String()) + confVal := cov.Get("value").String() + confValIsFile := cov.Get("isFile").Bool() + + conf, err := getConfig(confVal, confValIsFile, fs) + if err != nil { + return toJSError(err) + } + + var db *sql.DB + switch conf.DBType { + case "mysql": + db = sql.OpenDB(NewMyDBConn(dbv)) + default: + db = sql.OpenDB(NewPgDBConn(dbv)) + } + h := js.FuncOf(func(this js.Value, args []js.Value) interface{} { resolve := args[0] reject := args[1] go func() { - gj, err := newGraphJin(conf, confIsFile, db, fs) + gj, err := newGraphJin(conf, db, fs) if err != nil { reject.Invoke(toJSError(err)) } else { @@ -86,14 +98,12 @@ func newGraphJinObj(gj *core.GraphJin) map[string]interface{} { } } -func newGraphJin( - conf string, - confIsFile bool, - db *sql.DB, - fs plugin.FS) (gj *core.GraphJin, err error) { - - var config *core.Config +func newGraphJin(conf *core.Config, db *sql.DB, fs plugin.FS) (gj *core.GraphJin, err error) { + return core.NewGraphJinWithFS(conf, db, fs) +} +func getConfig(conf string, confIsFile bool, fs plugin.FS) ( + config *core.Config, err error) { if confIsFile { if config, err = core.NewConfigWithFS(fs, conf); err != nil { return nil, err @@ -105,6 +115,5 @@ func newGraphJin( } config = &c } - - return core.NewGraphJinWithFS(config, db, fs) + return } diff --git a/wasm/mysql.go b/wasm/mysql.go new file mode 100644 index 00000000..083458d4 --- /dev/null +++ b/wasm/mysql.go @@ -0,0 +1,201 @@ +//go:build wasm && js + +package main + +import ( + "context" + "database/sql/driver" + "errors" + "strconv" + "sync" + "syscall/js" +) + +type MyDB struct { +} + +func (d *MyDB) Open(name string) (driver.Conn, error) { + return nil, errors.New("use openwithclient") +} + +type MyConnector struct { + client js.Value + pool sync.Pool +} + +func (c *MyConnector) Connect(ctx context.Context) (driver.Conn, error) { + return &MyConn{client: c.client}, nil +} + +func (c *MyConnector) Driver() driver.Driver { + return &MyDB{} +} + +func NewMyDBConn(client js.Value) driver.Connector { + return &MyConnector{client: client} +} + +type MyConn struct { + client js.Value +} + +func (c *MyConn) Prepare(query string) (driver.Stmt, error) { + st := &MyStmt{ + client: c.client, + key: strconv.FormatUint(uint64(hash(query)), 10), + query: query, + numInput: -1, + } + return st, nil +} + +func (c *MyConn) QueryContext(ctx context.Context, query string, nargs []driver.NamedValue) (driver.Rows, error) { + args := make([]driver.Value, len(nargs)) + for _, v := range nargs { + args[(v.Ordinal - 1)] = v.Value + } + + st, err := c.Prepare(query) + if err != nil { + return nil, err + } + return st.Query(args) +} + +func (c *MyConn) Close() error { + return nil +} + +func (c *MyConn) Begin() (driver.Tx, error) { + return &MyTx{client: c.client}, nil +} + +type MyTx struct { + client js.Value +} + +func (t *MyTx) Begin() (driver.Tx, error) { + return t, nil +} + +func (t *MyTx) Commit() error { + return nil +} + +func (t *MyTx) Rollback() error { + return nil +} + +type MyStmt struct { + client js.Value + key string + query string + numInput int +} + +func (s *MyStmt) Close() error { + await(s.client.Call("end")) + return nil +} + +func (s *MyStmt) NumInput() int { + return s.numInput +} + +func (s *MyStmt) Exec(args []driver.Value) (driver.Result, error) { + res := &Result{lastInsertId: -1} + _, err := s.queryExec(args) + if err != nil { + return res, err + } + // res.rowsAffected = int64(v.Get("rowCount").Int()) + return res, nil +} + +func (s *MyStmt) Query(args []driver.Value) (driver.Rows, error) { + v, err := s.queryExec(args) + if err != nil { + return nil, err + } + + rows := v.Index(0) + cols := v.Index(1) + + // debug(rows) + + cl := cols.Length() + rl := rows.Length() + + ret := rowsPool.Get().(*Rows) + *ret = zeroRows // wipe clean for reuse + + ret.useArray = (cl == 1 && rl == 1 && rows.Index(0).Length() == 1) + + if ret.useArray { + ret.colsA[0] = cols.Index(0).Get("name").String() + ret.rowsA[0][0] = colVal(rows.Index(0).Index(0)) + return ret, nil + } + + ret.cols = make([]string, cols.Length()) + ret.rows = make([][]interface{}, rows.Length()) + + rowLen := -1 + if rl != 0 && rows.Index(0).Length() != 0 { + rowLen = rows.Index(0).Length() + } + + for i := 0; i < len(ret.cols); i++ { + name := cols.Index(i).Get("name").String() + ret.cols[i] = name + } + + for i := 0; i < len(ret.rows); i++ { + row := rows.Index(i) + ret.rows[i] = make([]interface{}, rowLen) + + for j := 0; j < rowLen; j++ { + ret.rows[i][j] = colVal(row.Index(j)) + } + } + + return ret, nil +} + +func getMyTypeParser() js.Func { + return js.FuncOf(func(this js.Value, args []js.Value) interface{} { + field := args[0] + next := args[1] + if field.Get("type").String() == "JSON" { + return field.Call("string") + } + return next.Invoke() + }) +} + +func (s *MyStmt) queryExec(args []driver.Value) (js.Value, error) { + m := map[string]interface{}{ + "rowsAsArray": true, + "typeCast": getMyTypeParser(), + "sql": s.query, + } + vals := make([]interface{}, len(args)) + for i, a := range args { + switch v := a.(type) { + case []byte: + vals[i] = js.ValueOf(string(v)) + default: + vals[i] = js.ValueOf(v) + } + } + m["values"] = vals + + res, rej := await(s.client.Call("query", m)) + + if len(rej) != 0 { + err := errors.New(rej[0].Get("message").String()) + return js.Null(), err + } + + return res[0], nil +} diff --git a/wasm/postgres.go b/wasm/postgres.go new file mode 100644 index 00000000..e5dd0f5f --- /dev/null +++ b/wasm/postgres.go @@ -0,0 +1,198 @@ +//go:build wasm && js + +package main + +import ( + "context" + "database/sql/driver" + "errors" + "strconv" + "sync" + "syscall/js" +) + +type PgDB struct { +} + +func (d *PgDB) Open(name string) (driver.Conn, error) { + return nil, errors.New("use openwithclient") +} + +type PgConnector struct { + client js.Value + pool sync.Pool +} + +func (c *PgConnector) Connect(ctx context.Context) (driver.Conn, error) { + return &PgConn{client: c.client}, nil +} + +func (c *PgConnector) Driver() driver.Driver { + return &PgDB{} +} + +func NewPgDBConn(client js.Value) driver.Connector { + return &PgConnector{client: client} +} + +type PgConn struct { + client js.Value +} + +func (c *PgConn) Prepare(query string) (driver.Stmt, error) { + st := &PgStmt{ + client: c.client, + key: strconv.FormatUint(uint64(hash(query)), 10), + query: query, + numInput: -1, + } + return st, nil +} + +func (c *PgConn) QueryContext(ctx context.Context, query string, nargs []driver.NamedValue) (driver.Rows, error) { + args := make([]driver.Value, len(nargs)) + for _, v := range nargs { + args[(v.Ordinal - 1)] = v.Value + } + + st, err := c.Prepare(query) + if err != nil { + return nil, err + } + return st.Query(args) +} + +func (c *PgConn) Close() error { + return nil +} + +func (c *PgConn) Begin() (driver.Tx, error) { + return &PgTx{client: c.client}, nil +} + +type PgTx struct { + client js.Value +} + +func (t *PgTx) Begin() (driver.Tx, error) { + return t, nil +} + +func (t *PgTx) Commit() error { + return nil +} + +func (t *PgTx) Rollback() error { + return nil +} + +type PgStmt struct { + client js.Value + key string + query string + numInput int +} + +func (s *PgStmt) Close() error { + await(s.client.Call("end")) + return nil +} + +func (s *PgStmt) NumInput() int { + return s.numInput +} + +func (s *PgStmt) Exec(args []driver.Value) (driver.Result, error) { + res := &Result{lastInsertId: -1} + v, err := s.queryExec(args) + if err != nil { + return res, err + } + res.rowsAffected = int64(v.Get("rowCount").Int()) + return res, nil +} + +func (s *PgStmt) Query(args []driver.Value) (driver.Rows, error) { + v, err := s.queryExec(args) + if err != nil { + return nil, err + } + + cols := v.Get("fields") + rows := v.Get("rows") + + cl := cols.Length() + rl := rows.Length() + + ret := rowsPool.Get().(*Rows) + *ret = zeroRows // wipe clean for reuse + + ret.useArray = (cl == 1 && rl == 1 && rows.Index(0).Length() == 1) + + if ret.useArray { + ret.colsA[0] = cols.Index(0).Get("name").String() + ret.rowsA[0][0] = colVal(rows.Index(0).Index(0)) + return ret, nil + } + + ret.cols = make([]string, cols.Length()) + ret.rows = make([][]interface{}, rows.Length()) + + rowLen := -1 + if rl != 0 && rows.Index(0).Length() != 0 { + rowLen = rows.Index(0).Length() + } + + for i := 0; i < len(ret.cols); i++ { + name := cols.Index(i).Get("name").String() + ret.cols[i] = name + } + + for i := 0; i < len(ret.rows); i++ { + row := rows.Index(i) + ret.rows[i] = make([]interface{}, rowLen) + + for j := 0; j < rowLen; j++ { + ret.rows[i][j] = colVal(row.Index(j)) + } + } + return ret, nil +} + +func getPGTypeParser() map[string]interface{} { + fn := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + return js.FuncOf(func(this js.Value, args []js.Value) interface{} { + return args[0] + }) + }) + return map[string]interface{}{"getTypeParser": fn} +} + +func (s *PgStmt) queryExec(args []driver.Value) (js.Value, error) { + m := map[string]interface{}{ + "rowMode": "array", + "types": getPGTypeParser(), + "name": s.key, + "text": s.query, + } + + vals := make([]interface{}, len(args)) + for i, a := range args { + switch v := a.(type) { + case []byte: + vals[i] = js.ValueOf(string(v)) + default: + vals[i] = js.ValueOf(v) + } + } + m["values"] = vals + + res, rej := await(s.client.Call("query", m)) + + if len(rej) != 0 { + err := errors.New(rej[0].Get("message").String()) + return js.Null(), err + } + + return res[0], nil +} diff --git a/wasm/util.go b/wasm/util.go index 3a52f206..b46fe902 100644 --- a/wasm/util.go +++ b/wasm/util.go @@ -4,6 +4,7 @@ package main import ( "errors" + "fmt" "syscall/js" ) @@ -54,3 +55,8 @@ func toError(err interface{}) error { func toJSError(err error) js.Value { return js.Global().Get("Error").New(err.Error()) } + +func debug(v js.Value) { + fmt.Println(js.Global().Get("JSON").Call("stringify", v)) + +} diff --git a/website/posts/9-nodejs.md b/website/posts/9-nodejs.md index e64aef65..f8111b75 100644 --- a/website/posts/9-nodejs.md +++ b/website/posts/9-nodejs.md @@ -46,22 +46,42 @@ const gj = await graphjin("./config", cf, db); ### Whats `db` ? -Its the database client, currently we only support the popular -Postgres client `pg`. Remeber to call `db.connect()` +Its the database client. We currently support the following popular clients. +For [pg](https://www.npmjs.com/package/pg) and [pg-pool](https://www.npmjs.com/package/pg-pool) for Postgres and [mysql2](https://www.npmjs.com/package/mysql2) for MySQL. We recommend using pooling clients for performance, `pg-pool`, `mysql2` has a `createPool()` function. ⚠️ We don't support mutations with MySQL and GraphJin requires the db client to support async/await (Promises) -```js -import pg from "pg"; +```js title="MySQL client setup" +import mysql from "mysql2"; -const { Client } = pg; -const db = new Client({ +const pool = mysql.createPool({ + host: "localhost", + port: "/tmp/mysql.sock", + user: "root", + database: "db", + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0, +}); + +// GraphJin requires the db client to support async/await (Promises) +const db = pool.promise(); + +// Set GraphJin config db_type to "mysql" for the MySQL client +const graphjinConfig = { production: false, db_type: "mysql" }; + +// Initialize GraphJin with this config and the MySQL client +const gj = await graphjin("./config", graphjinConfig, db); +``` + +```js title="Postgres client setup" +import PgPool from "pg-pool"; + +const db = new PgPool({ host: "localhost", port: 5432, user: "postgres", password: "postgres", - database: "appdb-development", + database: "42papers-development", }); - -await db.connect(); ``` ### Your first query @@ -169,3 +189,9 @@ res.data(function (res1) { {"users":{"email":"user3@test.com","id":3,"phone":"650-447-0007"}} {"users":{"email":"user3@test.com","id":3,"phone":"650-447-0008"}} ``` + +### Code examples + +1. [node prostgres.js](https://github.com/dosco/graphjin/blob/master/examples/nodejs/postgres.js) + +2. [node mysql.js](https://github.com/dosco/graphjin/blob/master/examples/nodejs/mysql.js)