Skip to content

Commit 72575ba

Browse files
feat: add local Unix domain socket support (#336)
Adds a new Connector.startLocalProxy method that starts a local proxy tunnel listening to the location defined at listenOptions enabling usage of the Connector by drivers and different libraries / frameworks that are not currently supported by the Connector directly but that can connect to databases via Unix sockets.
1 parent 404a85f commit 72575ba

20 files changed

+682
-11
lines changed

.github/workflows/tests.yml

+9-5
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ jobs:
108108

109109
- name: Run Tests
110110
env:
111-
TAP_DISABLE_COVERAGE: "${{ matrix.os == 'windows-latest' && '1' || '0' }}"
112-
TAP_ALLOW_MISSING_COVERAGE: "${{ matrix.os == 'windows-latest' && '1' || '0' }}"
113-
TAP_ALLOW_INCOMPLETE_COVERAGE: "${{ matrix.os == 'windows-latest' && '1' || '0' }}"
114-
TAP_ALLOW_EMPTY_COVERAGE: "${{ matrix.os == 'windows-latest' && '1' || '0' }}"
111+
TAP_DISABLE_COVERAGE: "1"
112+
TAP_ALLOW_MISSING_COVERAGE: "1"
113+
TAP_ALLOW_INCOMPLETE_COVERAGE: "1"
114+
TAP_ALLOW_EMPTY_COVERAGE: "1"
115115
if: "${{ matrix.node-version != 'v14.x' }}"
116116
run: npx tap -c -t0 -o test_results.tap test
117117
timeout-minutes: 5
@@ -335,6 +335,10 @@ jobs:
335335
- name: Install dependencies
336336
run: npm ci
337337

338+
- name: Install Prisma on node v16.x and up
339+
if: "${{ matrix.node-version != 'v14.x' }}"
340+
run: npm install prisma
341+
338342
- name: Setup self-direct dependency
339343
run: npm link
340344

@@ -392,5 +396,5 @@ jobs:
392396
SQLSERVER_USER: '${{ steps.secrets.outputs.SQLSERVER_USER }}'
393397
SQLSERVER_PASS: '${{ steps.secrets.outputs.SQLSERVER_PASS }}'
394398
SQLSERVER_DB: '${{ steps.secrets.outputs.SQLSERVER_DB }}'
395-
run: npx tap -c -t0 --disable-coverage --allow-empty-coverage examples/*/{mysql2,pg,tedious}/test/*{.cjs,.mjs,.ts} -o test_results.tap
399+
run: npx tap -c -t0 --disable-coverage --allow-empty-coverage examples/*/*/test/*{.cjs,.mjs,.ts} -o test_results.tap
396400
timeout-minutes: 5

README.md

+34-1
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,39 @@ connection.close();
202202
connector.close();
203203
```
204204

205+
### Using a Local Proxy Tunnel (Unix domain socket)
206+
207+
Another possible way to use the Cloud SQL Node.js Connector is by creating a
208+
local proxy server that tunnels to the secured connection established
209+
using the `Connector.startLocalProxy()` method instead of
210+
`Connector.getOptions()`.
211+
212+
This alternative approach enables usage of the Connector library with
213+
unsupported drivers such as [Prisma](https://www.prisma.io/). Here is an
214+
example on how to use it with its PostgreSQL driver:
215+
216+
```js
217+
import {Connector} from '@google-cloud/cloud-sql-connector';
218+
import {PrismaClient} from '@prisma/client';
219+
220+
const connector = new Connector();
221+
await connector.startLocalProxy({
222+
instanceConnectionName: 'my-project:us-east1:my-instance',
223+
listenOptions: { path: '.s.PGSQL.5432' },
224+
});
225+
const hostPath = process.cwd();
226+
227+
const datasourceUrl =
228+
`postgresql://my-user:password@localhost/dbName?host=${hostPath}`;
229+
const prisma = new PrismaClient({ datasourceUrl });
230+
231+
connector.close();
232+
await prisma.$disconnect();
233+
```
234+
235+
For examples on each of the supported Cloud SQL databases consult our
236+
[Prisma samples](./examples/README.md#prisma).
237+
205238
### Specifying IP Address Type
206239

207240
The Cloud SQL Connector for Node.js can be used to connect to Cloud SQL
@@ -457,4 +490,4 @@ Apache Version 2.0
457490
See [LICENSE](./LICENSE)
458491

459492
[credentials-json-file]: https://github.com/googleapis/google-cloud-node#download-your-service-account-credentials-json-file
460-
[google-auth-creds]: https://cloud.google.com/nodejs/docs/reference/google-auth-library/latest#loading-credentials-from-environment-variables
493+
[google-auth-creds]: https://cloud.google.com/nodejs/docs/reference/google-auth-library/latest#loading-credentials-from-environment-variables

examples/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ some of the most popular database libraries and frameworks.
66

77
## Available Examples
88

9+
### Prisma
10+
11+
- [MySQL (CommonJS)](./prisma/mysql/connect.cjs)
12+
- [MySQL (ESM)](./prisma/mysql/connect.mjs)
13+
- [MySQL (TypeScript)](./prisma/mysql/connect.ts)
14+
- [PostgreSQL (CommonJS)](./prisma/postgresql/connect.cjs)
15+
- [PostgreSQL (ESM)](./prisma/postgresql/connect.mjs)
16+
- [PostgreSQL (TypeScript)](./prisma/postgresql/connect.ts)
17+
918
### Knex
1019

1120
- [MySQL (CommonJS)](./knex/mysql2/connect.cjs)

examples/prisma/mysql/connect.cjs

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
const {resolve} = require('node:path');
16+
const {Connector} = require('@google-cloud/cloud-sql-connector');
17+
const {PrismaClient} = require('@prisma/client');
18+
19+
async function connect({instanceConnectionName, user, database}) {
20+
const path = resolve('mysql.socket');
21+
const connector = new Connector();
22+
await connector.startLocalProxy({
23+
instanceConnectionName,
24+
ipType: 'PUBLIC',
25+
authType: 'IAM',
26+
listenOptions: {path},
27+
});
28+
29+
const datasourceUrl = `mysql://${user}@localhost/${database}?socket=${path}`;
30+
const prisma = new PrismaClient({datasourceUrl});
31+
32+
return {
33+
prisma,
34+
async close() {
35+
await prisma.$disconnect();
36+
connector.close();
37+
},
38+
};
39+
}
40+
41+
module.exports = {
42+
connect,
43+
};

examples/prisma/mysql/connect.mjs

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {resolve} from 'node:path';
16+
import {Connector} from '@google-cloud/cloud-sql-connector';
17+
import {PrismaClient} from '@prisma/client';
18+
19+
export async function connect({instanceConnectionName, user, database}) {
20+
const path = resolve('mysql.socket');
21+
const connector = new Connector();
22+
await connector.startLocalProxy({
23+
instanceConnectionName,
24+
ipType: 'PUBLIC',
25+
authType: 'IAM',
26+
listenOptions: {path},
27+
});
28+
29+
const datasourceUrl = `mysql://${user}@localhost/${database}?socket=${path}`;
30+
const prisma = new PrismaClient({datasourceUrl});
31+
32+
return {
33+
prisma,
34+
async close() {
35+
await prisma.$disconnect();
36+
connector.close();
37+
},
38+
};
39+
}

examples/prisma/mysql/connect.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {resolve} from 'node:path';
16+
import {
17+
AuthTypes,
18+
Connector,
19+
IpAddressTypes,
20+
} from '@google-cloud/cloud-sql-connector';
21+
import {PrismaClient} from '@prisma/client';
22+
23+
export async function connect({instanceConnectionName, user, database}) {
24+
const path = resolve('mysql.socket');
25+
const connector = new Connector();
26+
await connector.startLocalProxy({
27+
instanceConnectionName,
28+
ipType: IpAddressTypes.PUBLIC,
29+
authType: AuthTypes.IAM,
30+
listenOptions: {path},
31+
});
32+
33+
const datasourceUrl = `mysql://${user}@localhost/${database}?socket=${path}`;
34+
const prisma = new PrismaClient({datasourceUrl});
35+
36+
return {
37+
prisma,
38+
async close() {
39+
await prisma.$disconnect();
40+
connector.close();
41+
},
42+
};
43+
}

examples/prisma/mysql/schema.prisma

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
datasource db {
2+
provider = "mysql"
3+
url = env("DB_URL")
4+
}
5+
6+
model User {
7+
id Int @id @default(autoincrement())
8+
}
9+
10+
generator client {
11+
provider = "prisma-client-js"
12+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
const {execSync} = require('node:child_process');
16+
const {resolve} = require('node:path');
17+
const t = require('tap');
18+
19+
function generatePrismaClient() {
20+
const schemaPath = resolve(__dirname, '../schema.prisma');
21+
22+
execSync(`npx prisma generate --schema=${schemaPath}`);
23+
}
24+
25+
t.test('mysql prisma cjs', async t => {
26+
// prisma client generation should normally be part of a regular Prisma
27+
// setup on user end but in order to tests in many different databases
28+
// we run the generation step at runtime for each variation
29+
generatePrismaClient();
30+
31+
const {connect} = require('../connect.cjs');
32+
const {prisma, close} = await connect({
33+
instanceConnectionName: process.env.MYSQL_IAM_CONNECTION_NAME,
34+
user: process.env.MYSQL_IAM_USER,
35+
database: process.env.MYSQL_DB,
36+
});
37+
const [{now}] = await prisma.$queryRaw`SELECT NOW() as now`;
38+
t.ok(now.getTime(), 'should have valid returned date object');
39+
await close();
40+
});
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { execSync } from 'node:child_process';
16+
import { dirname, resolve } from 'node:path'
17+
import { fileURLToPath } from 'node:url'
18+
import t from 'tap';
19+
20+
function generatePrismaClient() {
21+
const p = fileURLToPath(import.meta.url)
22+
const __dirname = dirname(p)
23+
const schemaPath = resolve(__dirname, '../schema.prisma');
24+
25+
execSync(`npx prisma generate --schema=${schemaPath}`);
26+
}
27+
28+
t.test('mysql prisma mjs', async t => {
29+
// prisma client generation should normally be part of a regular Prisma
30+
// setup on user end but in order to tests in many different databases
31+
// we run the generation step at runtime for each variation
32+
generatePrismaClient();
33+
34+
const { connect } = await import('../connect.mjs');
35+
const { prisma, close } = await connect({
36+
instanceConnectionName: process.env.MYSQL_IAM_CONNECTION_NAME,
37+
user: process.env.MYSQL_IAM_USER,
38+
database: process.env.MYSQL_DB,
39+
});
40+
const [{ now }] = await prisma.$queryRaw`SELECT NOW() as now`
41+
t.ok(now.getTime(), 'should have valid returned date object');
42+
await close();
43+
});

examples/prisma/mysql/test/connect.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {execSync} from 'node:child_process';
16+
import {resolve} from 'node:path';
17+
import t from 'tap';
18+
19+
function generatePrismaClient() {
20+
const schemaPath = resolve(__dirname, '../schema.prisma');
21+
22+
execSync(`npx prisma generate --schema=${schemaPath}`);
23+
}
24+
25+
t.test('mysql prisma ts', async t => {
26+
// prisma client generation should normally be part of a regular Prisma
27+
// setup on user end but in order to tests in many different databases
28+
// we run the generation step at runtime for each variation
29+
generatePrismaClient();
30+
31+
const {
32+
default: {connect},
33+
} = await import('../connect.ts');
34+
const {prisma, close} = await connect({
35+
instanceConnectionName: process.env.MYSQL_IAM_CONNECTION_NAME,
36+
user: process.env.MYSQL_IAM_USER,
37+
database: process.env.MYSQL_DB,
38+
});
39+
const [{now}] = await prisma.$queryRaw`SELECT NOW() as now`;
40+
t.ok(now.getTime(), 'should have valid returned date object');
41+
await close();
42+
});

0 commit comments

Comments
 (0)