@budibase/server 2.22.15 → 2.22.16
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/builder/assets/{index-d3456412.js → index-4c998325.js} +492 -492
- package/builder/index.html +1 -1
- package/client/budibase-client.js +2 -2
- package/dist/yarn.lock +24 -36
- package/package.json +9 -9
- package/scripts/test.sh +2 -2
- package/src/api/routes/public/tests/metrics.spec.js +0 -2
- package/src/api/routes/tests/appImport.spec.ts +0 -1
- package/src/api/routes/tests/automation.spec.ts +0 -2
- package/src/api/routes/tests/queries/generic-sql.spec.ts +34 -59
- package/src/api/routes/tests/queries/mongodb.spec.ts +17 -12
- package/src/api/routes/tests/row.spec.ts +18 -21
- package/src/api/routes/tests/user.spec.ts +0 -2
- package/src/api/routes/tests/viewV2.spec.ts +13 -18
- package/src/integration-test/mysql.spec.ts +61 -72
- package/src/integration-test/postgres.spec.ts +65 -61
- package/src/integrations/tests/utils/index.ts +78 -13
- package/src/integrations/tests/utils/mariadb.ts +31 -28
- package/src/integrations/tests/utils/mongodb.ts +25 -29
- package/src/integrations/tests/utils/mssql.ts +51 -36
- package/src/integrations/tests/utils/mysql.ts +36 -21
- package/src/integrations/tests/utils/postgres.ts +43 -26
- package/src/migrations/tests/index.spec.ts +0 -2
- package/src/sdk/app/rows/search/tests/external.spec.ts +0 -2
- package/src/tests/jestSetup.ts +2 -8
- package/src/tests/utilities/api/base.ts +41 -11
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
generateMakeRequest,
|
|
4
4
|
MakeRequestResponse,
|
|
5
5
|
} from "../api/routes/public/tests/utils"
|
|
6
|
-
import { v4 as uuidv4 } from "uuid"
|
|
7
6
|
import * as setup from "../api/routes/tests/utilities"
|
|
8
7
|
import {
|
|
9
8
|
Datasource,
|
|
@@ -12,12 +11,23 @@ import {
|
|
|
12
11
|
TableRequest,
|
|
13
12
|
TableSourceType,
|
|
14
13
|
} from "@budibase/types"
|
|
15
|
-
import {
|
|
16
|
-
|
|
14
|
+
import {
|
|
15
|
+
DatabaseName,
|
|
16
|
+
getDatasource,
|
|
17
|
+
rawQuery,
|
|
18
|
+
} from "../integrations/tests/utils"
|
|
17
19
|
import { builderSocket } from "../websockets"
|
|
20
|
+
import { generator } from "@budibase/backend-core/tests"
|
|
18
21
|
// @ts-ignore
|
|
19
22
|
fetch.mockSearch()
|
|
20
23
|
|
|
24
|
+
function uniqueTableName(length?: number): string {
|
|
25
|
+
return generator
|
|
26
|
+
.guid()
|
|
27
|
+
.replaceAll("-", "_")
|
|
28
|
+
.substring(0, length || 10)
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
const config = setup.getConfig()!
|
|
22
32
|
|
|
23
33
|
jest.mock("../websockets", () => ({
|
|
@@ -37,7 +47,8 @@ jest.mock("../websockets", () => ({
|
|
|
37
47
|
|
|
38
48
|
describe("mysql integrations", () => {
|
|
39
49
|
let makeRequest: MakeRequestResponse,
|
|
40
|
-
|
|
50
|
+
rawDatasource: Datasource,
|
|
51
|
+
datasource: Datasource,
|
|
41
52
|
primaryMySqlTable: Table
|
|
42
53
|
|
|
43
54
|
beforeAll(async () => {
|
|
@@ -46,18 +57,13 @@ describe("mysql integrations", () => {
|
|
|
46
57
|
|
|
47
58
|
makeRequest = generateMakeRequest(apiKey, true)
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
afterAll(async () => {
|
|
55
|
-
await databaseTestProviders.mysql.stop()
|
|
60
|
+
rawDatasource = await getDatasource(DatabaseName.MYSQL)
|
|
61
|
+
datasource = await config.api.datasource.create(rawDatasource)
|
|
56
62
|
})
|
|
57
63
|
|
|
58
64
|
beforeEach(async () => {
|
|
59
65
|
primaryMySqlTable = await config.createTable({
|
|
60
|
-
name:
|
|
66
|
+
name: uniqueTableName(),
|
|
61
67
|
type: "table",
|
|
62
68
|
primary: ["id"],
|
|
63
69
|
schema: {
|
|
@@ -79,7 +85,7 @@ describe("mysql integrations", () => {
|
|
|
79
85
|
type: FieldType.NUMBER,
|
|
80
86
|
},
|
|
81
87
|
},
|
|
82
|
-
sourceId:
|
|
88
|
+
sourceId: datasource._id,
|
|
83
89
|
sourceType: TableSourceType.EXTERNAL,
|
|
84
90
|
})
|
|
85
91
|
})
|
|
@@ -87,18 +93,15 @@ describe("mysql integrations", () => {
|
|
|
87
93
|
afterAll(config.end)
|
|
88
94
|
|
|
89
95
|
it("validate table schema", async () => {
|
|
90
|
-
const res = await makeRequest(
|
|
91
|
-
"get",
|
|
92
|
-
`/api/datasources/${mysqlDatasource._id}`
|
|
93
|
-
)
|
|
96
|
+
const res = await makeRequest("get", `/api/datasources/${datasource._id}`)
|
|
94
97
|
|
|
95
98
|
expect(res.status).toBe(200)
|
|
96
99
|
expect(res.body).toEqual({
|
|
97
100
|
config: {
|
|
98
|
-
database:
|
|
99
|
-
host:
|
|
101
|
+
database: expect.any(String),
|
|
102
|
+
host: datasource.config!.host,
|
|
100
103
|
password: "--secret-value--",
|
|
101
|
-
port:
|
|
104
|
+
port: datasource.config!.port,
|
|
102
105
|
user: "root",
|
|
103
106
|
},
|
|
104
107
|
plus: true,
|
|
@@ -117,7 +120,7 @@ describe("mysql integrations", () => {
|
|
|
117
120
|
it("should be able to verify the connection", async () => {
|
|
118
121
|
await config.api.datasource.verify(
|
|
119
122
|
{
|
|
120
|
-
datasource:
|
|
123
|
+
datasource: rawDatasource,
|
|
121
124
|
},
|
|
122
125
|
{
|
|
123
126
|
body: {
|
|
@@ -128,13 +131,12 @@ describe("mysql integrations", () => {
|
|
|
128
131
|
})
|
|
129
132
|
|
|
130
133
|
it("should state an invalid datasource cannot connect", async () => {
|
|
131
|
-
const dbConfig = await databaseTestProviders.mysql.datasource()
|
|
132
134
|
await config.api.datasource.verify(
|
|
133
135
|
{
|
|
134
136
|
datasource: {
|
|
135
|
-
...
|
|
137
|
+
...rawDatasource,
|
|
136
138
|
config: {
|
|
137
|
-
...
|
|
139
|
+
...rawDatasource.config,
|
|
138
140
|
password: "wrongpassword",
|
|
139
141
|
},
|
|
140
142
|
},
|
|
@@ -154,7 +156,7 @@ describe("mysql integrations", () => {
|
|
|
154
156
|
it("should fetch information about mysql datasource", async () => {
|
|
155
157
|
const primaryName = primaryMySqlTable.name
|
|
156
158
|
const response = await makeRequest("post", "/api/datasources/info", {
|
|
157
|
-
datasource:
|
|
159
|
+
datasource: datasource,
|
|
158
160
|
})
|
|
159
161
|
expect(response.status).toBe(200)
|
|
160
162
|
expect(response.body.tableNames).toBeDefined()
|
|
@@ -163,40 +165,38 @@ describe("mysql integrations", () => {
|
|
|
163
165
|
})
|
|
164
166
|
|
|
165
167
|
describe("Integration compatibility with mysql search_path", () => {
|
|
166
|
-
let
|
|
167
|
-
const database =
|
|
168
|
-
const database2 =
|
|
168
|
+
let datasource: Datasource, rawDatasource: Datasource
|
|
169
|
+
const database = generator.guid()
|
|
170
|
+
const database2 = generator.guid()
|
|
169
171
|
|
|
170
172
|
beforeAll(async () => {
|
|
171
|
-
|
|
172
|
-
const dbConfig = dsConfig.config!
|
|
173
|
+
rawDatasource = await getDatasource(DatabaseName.MYSQL)
|
|
173
174
|
|
|
174
|
-
|
|
175
|
-
await
|
|
176
|
-
await client.query(`CREATE DATABASE \`${database2}\`;`)
|
|
175
|
+
await rawQuery(rawDatasource, `CREATE DATABASE \`${database}\`;`)
|
|
176
|
+
await rawQuery(rawDatasource, `CREATE DATABASE \`${database2}\`;`)
|
|
177
177
|
|
|
178
178
|
const pathConfig: any = {
|
|
179
|
-
...
|
|
179
|
+
...rawDatasource,
|
|
180
180
|
config: {
|
|
181
|
-
...
|
|
181
|
+
...rawDatasource.config!,
|
|
182
182
|
database,
|
|
183
183
|
},
|
|
184
184
|
}
|
|
185
|
-
|
|
185
|
+
datasource = await config.api.datasource.create(pathConfig)
|
|
186
186
|
})
|
|
187
187
|
|
|
188
188
|
afterAll(async () => {
|
|
189
|
-
await
|
|
190
|
-
await
|
|
191
|
-
await client.end()
|
|
189
|
+
await rawQuery(rawDatasource, `DROP DATABASE \`${database}\`;`)
|
|
190
|
+
await rawQuery(rawDatasource, `DROP DATABASE \`${database2}\`;`)
|
|
192
191
|
})
|
|
193
192
|
|
|
194
193
|
it("discovers tables from any schema in search path", async () => {
|
|
195
|
-
await
|
|
194
|
+
await rawQuery(
|
|
195
|
+
rawDatasource,
|
|
196
196
|
`CREATE TABLE \`${database}\`.table1 (id1 SERIAL PRIMARY KEY);`
|
|
197
197
|
)
|
|
198
198
|
const response = await makeRequest("post", "/api/datasources/info", {
|
|
199
|
-
datasource:
|
|
199
|
+
datasource: datasource,
|
|
200
200
|
})
|
|
201
201
|
expect(response.status).toBe(200)
|
|
202
202
|
expect(response.body.tableNames).toBeDefined()
|
|
@@ -207,15 +207,17 @@ describe("mysql integrations", () => {
|
|
|
207
207
|
|
|
208
208
|
it("does not mix columns from different tables", async () => {
|
|
209
209
|
const repeated_table_name = "table_same_name"
|
|
210
|
-
await
|
|
210
|
+
await rawQuery(
|
|
211
|
+
rawDatasource,
|
|
211
212
|
`CREATE TABLE \`${database}\`.${repeated_table_name} (id SERIAL PRIMARY KEY, val1 TEXT);`
|
|
212
213
|
)
|
|
213
|
-
await
|
|
214
|
+
await rawQuery(
|
|
215
|
+
rawDatasource,
|
|
214
216
|
`CREATE TABLE \`${database2}\`.${repeated_table_name} (id2 SERIAL PRIMARY KEY, val2 TEXT);`
|
|
215
217
|
)
|
|
216
218
|
const response = await makeRequest(
|
|
217
219
|
"post",
|
|
218
|
-
`/api/datasources/${
|
|
220
|
+
`/api/datasources/${datasource._id}/schema`,
|
|
219
221
|
{
|
|
220
222
|
tablesFilter: [repeated_table_name],
|
|
221
223
|
}
|
|
@@ -231,30 +233,14 @@ describe("mysql integrations", () => {
|
|
|
231
233
|
})
|
|
232
234
|
|
|
233
235
|
describe("POST /api/tables/", () => {
|
|
234
|
-
let client: mysql.Connection
|
|
235
236
|
const emitDatasourceUpdateMock = jest.fn()
|
|
236
237
|
|
|
237
|
-
beforeEach(async () => {
|
|
238
|
-
client = await mysql.createConnection(
|
|
239
|
-
(
|
|
240
|
-
await databaseTestProviders.mysql.datasource()
|
|
241
|
-
).config!
|
|
242
|
-
)
|
|
243
|
-
mysqlDatasource = await config.api.datasource.create(
|
|
244
|
-
await databaseTestProviders.mysql.datasource()
|
|
245
|
-
)
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
afterEach(async () => {
|
|
249
|
-
await client.end()
|
|
250
|
-
})
|
|
251
|
-
|
|
252
238
|
it("will emit the datasource entity schema with externalType to the front-end when adding a new column", async () => {
|
|
253
239
|
const addColumnToTable: TableRequest = {
|
|
254
240
|
type: "table",
|
|
255
241
|
sourceType: TableSourceType.EXTERNAL,
|
|
256
|
-
name:
|
|
257
|
-
sourceId:
|
|
242
|
+
name: uniqueTableName(),
|
|
243
|
+
sourceId: datasource._id!,
|
|
258
244
|
primary: ["id"],
|
|
259
245
|
schema: {
|
|
260
246
|
id: {
|
|
@@ -301,14 +287,16 @@ describe("mysql integrations", () => {
|
|
|
301
287
|
},
|
|
302
288
|
},
|
|
303
289
|
created: true,
|
|
304
|
-
_id: `${
|
|
290
|
+
_id: `${datasource._id}__${addColumnToTable.name}`,
|
|
305
291
|
}
|
|
306
292
|
delete expectedTable._add
|
|
307
293
|
|
|
308
294
|
expect(emitDatasourceUpdateMock).toHaveBeenCalledTimes(1)
|
|
309
295
|
const emittedDatasource: Datasource =
|
|
310
296
|
emitDatasourceUpdateMock.mock.calls[0][1]
|
|
311
|
-
expect(emittedDatasource.entities![
|
|
297
|
+
expect(emittedDatasource.entities![expectedTable.name]).toEqual(
|
|
298
|
+
expectedTable
|
|
299
|
+
)
|
|
312
300
|
})
|
|
313
301
|
|
|
314
302
|
it("will rename a column", async () => {
|
|
@@ -346,17 +334,18 @@ describe("mysql integrations", () => {
|
|
|
346
334
|
"/api/tables/",
|
|
347
335
|
renameColumnOnTable
|
|
348
336
|
)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
`/api/datasources/${mysqlDatasource._id}/schema`
|
|
353
|
-
)
|
|
337
|
+
|
|
338
|
+
const ds = (
|
|
339
|
+
await makeRequest("post", `/api/datasources/${datasource._id}/schema`)
|
|
354
340
|
).body.datasource
|
|
355
341
|
|
|
356
342
|
expect(response.status).toEqual(200)
|
|
357
|
-
expect(
|
|
358
|
-
|
|
359
|
-
|
|
343
|
+
expect(Object.keys(ds.entities![primaryMySqlTable.name].schema)).toEqual([
|
|
344
|
+
"id",
|
|
345
|
+
"name",
|
|
346
|
+
"description",
|
|
347
|
+
"age",
|
|
348
|
+
])
|
|
360
349
|
})
|
|
361
350
|
})
|
|
362
351
|
})
|
|
@@ -16,8 +16,12 @@ import {
|
|
|
16
16
|
import _ from "lodash"
|
|
17
17
|
import { generator } from "@budibase/backend-core/tests"
|
|
18
18
|
import { utils } from "@budibase/backend-core"
|
|
19
|
-
import {
|
|
20
|
-
|
|
19
|
+
import {
|
|
20
|
+
DatabaseName,
|
|
21
|
+
getDatasource,
|
|
22
|
+
rawQuery,
|
|
23
|
+
} from "../integrations/tests/utils"
|
|
24
|
+
|
|
21
25
|
// @ts-ignore
|
|
22
26
|
fetch.mockSearch()
|
|
23
27
|
|
|
@@ -28,7 +32,8 @@ jest.mock("../websockets")
|
|
|
28
32
|
|
|
29
33
|
describe("postgres integrations", () => {
|
|
30
34
|
let makeRequest: MakeRequestResponse,
|
|
31
|
-
|
|
35
|
+
rawDatasource: Datasource,
|
|
36
|
+
datasource: Datasource,
|
|
32
37
|
primaryPostgresTable: Table,
|
|
33
38
|
oneToManyRelationshipInfo: ForeignTableInfo,
|
|
34
39
|
manyToOneRelationshipInfo: ForeignTableInfo,
|
|
@@ -40,19 +45,17 @@ describe("postgres integrations", () => {
|
|
|
40
45
|
|
|
41
46
|
makeRequest = generateMakeRequest(apiKey, true)
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
afterAll(async () => {
|
|
49
|
-
await databaseTestProviders.postgres.stop()
|
|
48
|
+
rawDatasource = await getDatasource(DatabaseName.POSTGRES)
|
|
49
|
+
datasource = await config.api.datasource.create(rawDatasource)
|
|
50
50
|
})
|
|
51
51
|
|
|
52
52
|
beforeEach(async () => {
|
|
53
53
|
async function createAuxTable(prefix: string) {
|
|
54
54
|
return await config.createTable({
|
|
55
|
-
name: `${prefix}_${generator
|
|
55
|
+
name: `${prefix}_${generator
|
|
56
|
+
.guid()
|
|
57
|
+
.replaceAll("-", "")
|
|
58
|
+
.substring(0, 6)}`,
|
|
56
59
|
type: "table",
|
|
57
60
|
primary: ["id"],
|
|
58
61
|
primaryDisplay: "title",
|
|
@@ -67,7 +70,7 @@ describe("postgres integrations", () => {
|
|
|
67
70
|
type: FieldType.STRING,
|
|
68
71
|
},
|
|
69
72
|
},
|
|
70
|
-
sourceId:
|
|
73
|
+
sourceId: datasource._id,
|
|
71
74
|
sourceType: TableSourceType.EXTERNAL,
|
|
72
75
|
})
|
|
73
76
|
}
|
|
@@ -89,7 +92,7 @@ describe("postgres integrations", () => {
|
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
primaryPostgresTable = await config.createTable({
|
|
92
|
-
name: `p_${generator.
|
|
95
|
+
name: `p_${generator.guid().replaceAll("-", "").substring(0, 6)}`,
|
|
93
96
|
type: "table",
|
|
94
97
|
primary: ["id"],
|
|
95
98
|
schema: {
|
|
@@ -144,7 +147,7 @@ describe("postgres integrations", () => {
|
|
|
144
147
|
main: true,
|
|
145
148
|
},
|
|
146
149
|
},
|
|
147
|
-
sourceId:
|
|
150
|
+
sourceId: datasource._id,
|
|
148
151
|
sourceType: TableSourceType.EXTERNAL,
|
|
149
152
|
})
|
|
150
153
|
})
|
|
@@ -251,7 +254,7 @@ describe("postgres integrations", () => {
|
|
|
251
254
|
|
|
252
255
|
async function createDefaultPgTable() {
|
|
253
256
|
return await config.createTable({
|
|
254
|
-
name: generator.
|
|
257
|
+
name: generator.guid().replaceAll("-", "").substring(0, 10),
|
|
255
258
|
type: "table",
|
|
256
259
|
primary: ["id"],
|
|
257
260
|
schema: {
|
|
@@ -261,7 +264,7 @@ describe("postgres integrations", () => {
|
|
|
261
264
|
autocolumn: true,
|
|
262
265
|
},
|
|
263
266
|
},
|
|
264
|
-
sourceId:
|
|
267
|
+
sourceId: datasource._id,
|
|
265
268
|
sourceType: TableSourceType.EXTERNAL,
|
|
266
269
|
})
|
|
267
270
|
}
|
|
@@ -299,19 +302,16 @@ describe("postgres integrations", () => {
|
|
|
299
302
|
}
|
|
300
303
|
|
|
301
304
|
it("validate table schema", async () => {
|
|
302
|
-
const res = await makeRequest(
|
|
303
|
-
"get",
|
|
304
|
-
`/api/datasources/${postgresDatasource._id}`
|
|
305
|
-
)
|
|
305
|
+
const res = await makeRequest("get", `/api/datasources/${datasource._id}`)
|
|
306
306
|
|
|
307
307
|
expect(res.status).toBe(200)
|
|
308
308
|
expect(res.body).toEqual({
|
|
309
309
|
config: {
|
|
310
310
|
ca: false,
|
|
311
|
-
database:
|
|
312
|
-
host:
|
|
311
|
+
database: expect.any(String),
|
|
312
|
+
host: datasource.config!.host,
|
|
313
313
|
password: "--secret-value--",
|
|
314
|
-
port:
|
|
314
|
+
port: datasource.config!.port,
|
|
315
315
|
rejectUnauthorized: false,
|
|
316
316
|
schema: "public",
|
|
317
317
|
ssl: false,
|
|
@@ -1043,7 +1043,7 @@ describe("postgres integrations", () => {
|
|
|
1043
1043
|
it("should be able to verify the connection", async () => {
|
|
1044
1044
|
await config.api.datasource.verify(
|
|
1045
1045
|
{
|
|
1046
|
-
datasource: await
|
|
1046
|
+
datasource: await getDatasource(DatabaseName.POSTGRES),
|
|
1047
1047
|
},
|
|
1048
1048
|
{
|
|
1049
1049
|
body: {
|
|
@@ -1054,7 +1054,7 @@ describe("postgres integrations", () => {
|
|
|
1054
1054
|
})
|
|
1055
1055
|
|
|
1056
1056
|
it("should state an invalid datasource cannot connect", async () => {
|
|
1057
|
-
const dbConfig = await
|
|
1057
|
+
const dbConfig = await getDatasource(DatabaseName.POSTGRES)
|
|
1058
1058
|
await config.api.datasource.verify(
|
|
1059
1059
|
{
|
|
1060
1060
|
datasource: {
|
|
@@ -1079,7 +1079,7 @@ describe("postgres integrations", () => {
|
|
|
1079
1079
|
it("should fetch information about postgres datasource", async () => {
|
|
1080
1080
|
const primaryName = primaryPostgresTable.name
|
|
1081
1081
|
const response = await makeRequest("post", "/api/datasources/info", {
|
|
1082
|
-
datasource:
|
|
1082
|
+
datasource: datasource,
|
|
1083
1083
|
})
|
|
1084
1084
|
expect(response.status).toBe(200)
|
|
1085
1085
|
expect(response.body.tableNames).toBeDefined()
|
|
@@ -1088,86 +1088,88 @@ describe("postgres integrations", () => {
|
|
|
1088
1088
|
})
|
|
1089
1089
|
|
|
1090
1090
|
describe("POST /api/datasources/:datasourceId/schema", () => {
|
|
1091
|
-
let
|
|
1091
|
+
let tableName: string
|
|
1092
1092
|
|
|
1093
1093
|
beforeEach(async () => {
|
|
1094
|
-
|
|
1095
|
-
(await databaseTestProviders.postgres.datasource()).config!
|
|
1096
|
-
)
|
|
1097
|
-
await client.connect()
|
|
1094
|
+
tableName = generator.guid().replaceAll("-", "").substring(0, 10)
|
|
1098
1095
|
})
|
|
1099
1096
|
|
|
1100
1097
|
afterEach(async () => {
|
|
1101
|
-
await
|
|
1102
|
-
await client.end()
|
|
1098
|
+
await rawQuery(rawDatasource, `DROP TABLE IF EXISTS "${tableName}"`)
|
|
1103
1099
|
})
|
|
1104
1100
|
|
|
1105
1101
|
it("recognises when a table has no primary key", async () => {
|
|
1106
|
-
await
|
|
1102
|
+
await rawQuery(rawDatasource, `CREATE TABLE "${tableName}" (id SERIAL)`)
|
|
1107
1103
|
|
|
1108
1104
|
const response = await makeRequest(
|
|
1109
1105
|
"post",
|
|
1110
|
-
`/api/datasources/${
|
|
1106
|
+
`/api/datasources/${datasource._id}/schema`
|
|
1111
1107
|
)
|
|
1112
1108
|
|
|
1113
1109
|
expect(response.body.errors).toEqual({
|
|
1114
|
-
|
|
1110
|
+
[tableName]: "Table must have a primary key.",
|
|
1115
1111
|
})
|
|
1116
1112
|
})
|
|
1117
1113
|
|
|
1118
1114
|
it("recognises when a table is using a reserved column name", async () => {
|
|
1119
|
-
await
|
|
1115
|
+
await rawQuery(
|
|
1116
|
+
rawDatasource,
|
|
1117
|
+
`CREATE TABLE "${tableName}" (_id SERIAL PRIMARY KEY) `
|
|
1118
|
+
)
|
|
1120
1119
|
|
|
1121
1120
|
const response = await makeRequest(
|
|
1122
1121
|
"post",
|
|
1123
|
-
`/api/datasources/${
|
|
1122
|
+
`/api/datasources/${datasource._id}/schema`
|
|
1124
1123
|
)
|
|
1125
1124
|
|
|
1126
1125
|
expect(response.body.errors).toEqual({
|
|
1127
|
-
|
|
1126
|
+
[tableName]: "Table contains invalid columns.",
|
|
1128
1127
|
})
|
|
1129
1128
|
})
|
|
1130
1129
|
})
|
|
1131
1130
|
|
|
1132
1131
|
describe("Integration compatibility with postgres search_path", () => {
|
|
1133
|
-
let
|
|
1134
|
-
|
|
1135
|
-
|
|
1132
|
+
let rawDatasource: Datasource,
|
|
1133
|
+
datasource: Datasource,
|
|
1134
|
+
schema1: string,
|
|
1135
|
+
schema2: string
|
|
1136
1136
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1137
|
+
beforeEach(async () => {
|
|
1138
|
+
schema1 = generator.guid().replaceAll("-", "")
|
|
1139
|
+
schema2 = generator.guid().replaceAll("-", "")
|
|
1140
1140
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
await
|
|
1141
|
+
rawDatasource = await getDatasource(DatabaseName.POSTGRES)
|
|
1142
|
+
const dbConfig = rawDatasource.config!
|
|
1143
|
+
|
|
1144
|
+
await rawQuery(rawDatasource, `CREATE SCHEMA "${schema1}";`)
|
|
1145
|
+
await rawQuery(rawDatasource, `CREATE SCHEMA "${schema2}";`)
|
|
1145
1146
|
|
|
1146
1147
|
const pathConfig: any = {
|
|
1147
|
-
...
|
|
1148
|
+
...rawDatasource,
|
|
1148
1149
|
config: {
|
|
1149
1150
|
...dbConfig,
|
|
1150
1151
|
schema: `${schema1}, ${schema2}`,
|
|
1151
1152
|
},
|
|
1152
1153
|
}
|
|
1153
|
-
|
|
1154
|
+
datasource = await config.api.datasource.create(pathConfig)
|
|
1154
1155
|
})
|
|
1155
1156
|
|
|
1156
|
-
|
|
1157
|
-
await
|
|
1158
|
-
await
|
|
1159
|
-
await client.end()
|
|
1157
|
+
afterEach(async () => {
|
|
1158
|
+
await rawQuery(rawDatasource, `DROP SCHEMA "${schema1}" CASCADE;`)
|
|
1159
|
+
await rawQuery(rawDatasource, `DROP SCHEMA "${schema2}" CASCADE;`)
|
|
1160
1160
|
})
|
|
1161
1161
|
|
|
1162
1162
|
it("discovers tables from any schema in search path", async () => {
|
|
1163
|
-
await
|
|
1163
|
+
await rawQuery(
|
|
1164
|
+
rawDatasource,
|
|
1164
1165
|
`CREATE TABLE "${schema1}".table1 (id1 SERIAL PRIMARY KEY);`
|
|
1165
1166
|
)
|
|
1166
|
-
await
|
|
1167
|
+
await rawQuery(
|
|
1168
|
+
rawDatasource,
|
|
1167
1169
|
`CREATE TABLE "${schema2}".table2 (id2 SERIAL PRIMARY KEY);`
|
|
1168
1170
|
)
|
|
1169
1171
|
const response = await makeRequest("post", "/api/datasources/info", {
|
|
1170
|
-
datasource:
|
|
1172
|
+
datasource: datasource,
|
|
1171
1173
|
})
|
|
1172
1174
|
expect(response.status).toBe(200)
|
|
1173
1175
|
expect(response.body.tableNames).toBeDefined()
|
|
@@ -1178,15 +1180,17 @@ describe("postgres integrations", () => {
|
|
|
1178
1180
|
|
|
1179
1181
|
it("does not mix columns from different tables", async () => {
|
|
1180
1182
|
const repeated_table_name = "table_same_name"
|
|
1181
|
-
await
|
|
1183
|
+
await rawQuery(
|
|
1184
|
+
rawDatasource,
|
|
1182
1185
|
`CREATE TABLE "${schema1}".${repeated_table_name} (id SERIAL PRIMARY KEY, val1 TEXT);`
|
|
1183
1186
|
)
|
|
1184
|
-
await
|
|
1187
|
+
await rawQuery(
|
|
1188
|
+
rawDatasource,
|
|
1185
1189
|
`CREATE TABLE "${schema2}".${repeated_table_name} (id2 SERIAL PRIMARY KEY, val2 TEXT);`
|
|
1186
1190
|
)
|
|
1187
1191
|
const response = await makeRequest(
|
|
1188
1192
|
"post",
|
|
1189
|
-
`/api/datasources/${
|
|
1193
|
+
`/api/datasources/${datasource._id}/schema`,
|
|
1190
1194
|
{
|
|
1191
1195
|
tablesFilter: [repeated_table_name],
|
|
1192
1196
|
}
|
|
@@ -1,25 +1,90 @@
|
|
|
1
1
|
jest.unmock("pg")
|
|
2
2
|
|
|
3
|
-
import { Datasource } from "@budibase/types"
|
|
3
|
+
import { Datasource, SourceName } from "@budibase/types"
|
|
4
4
|
import * as postgres from "./postgres"
|
|
5
5
|
import * as mongodb from "./mongodb"
|
|
6
6
|
import * as mysql from "./mysql"
|
|
7
7
|
import * as mssql from "./mssql"
|
|
8
8
|
import * as mariadb from "./mariadb"
|
|
9
|
-
import {
|
|
9
|
+
import { GenericContainer } from "testcontainers"
|
|
10
|
+
import { testContainerUtils } from "@budibase/backend-core/tests"
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
export type DatasourceProvider = () => Promise<Datasource>
|
|
12
13
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
export enum DatabaseName {
|
|
15
|
+
POSTGRES = "postgres",
|
|
16
|
+
MONGODB = "mongodb",
|
|
17
|
+
MYSQL = "mysql",
|
|
18
|
+
SQL_SERVER = "mssql",
|
|
19
|
+
MARIADB = "mariadb",
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
postgres,
|
|
21
|
-
mongodb,
|
|
22
|
-
mysql,
|
|
23
|
-
mssql,
|
|
24
|
-
mariadb,
|
|
22
|
+
const providers: Record<DatabaseName, DatasourceProvider> = {
|
|
23
|
+
[DatabaseName.POSTGRES]: postgres.getDatasource,
|
|
24
|
+
[DatabaseName.MONGODB]: mongodb.getDatasource,
|
|
25
|
+
[DatabaseName.MYSQL]: mysql.getDatasource,
|
|
26
|
+
[DatabaseName.SQL_SERVER]: mssql.getDatasource,
|
|
27
|
+
[DatabaseName.MARIADB]: mariadb.getDatasource,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getDatasourceProviders(
|
|
31
|
+
...sourceNames: DatabaseName[]
|
|
32
|
+
): Promise<Datasource>[] {
|
|
33
|
+
return sourceNames.map(sourceName => providers[sourceName]())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getDatasourceProvider(
|
|
37
|
+
sourceName: DatabaseName
|
|
38
|
+
): DatasourceProvider {
|
|
39
|
+
return providers[sourceName]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getDatasource(sourceName: DatabaseName): Promise<Datasource> {
|
|
43
|
+
return providers[sourceName]()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function getDatasources(
|
|
47
|
+
...sourceNames: DatabaseName[]
|
|
48
|
+
): Promise<Datasource[]> {
|
|
49
|
+
return Promise.all(sourceNames.map(sourceName => providers[sourceName]()))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function rawQuery(ds: Datasource, sql: string): Promise<any> {
|
|
53
|
+
switch (ds.source) {
|
|
54
|
+
case SourceName.POSTGRES: {
|
|
55
|
+
return postgres.rawQuery(ds, sql)
|
|
56
|
+
}
|
|
57
|
+
case SourceName.MYSQL: {
|
|
58
|
+
return mysql.rawQuery(ds, sql)
|
|
59
|
+
}
|
|
60
|
+
case SourceName.SQL_SERVER: {
|
|
61
|
+
return mssql.rawQuery(ds, sql)
|
|
62
|
+
}
|
|
63
|
+
default: {
|
|
64
|
+
throw new Error(`Unsupported source: ${ds.source}`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function startContainer(container: GenericContainer) {
|
|
70
|
+
if (process.env.REUSE_CONTAINERS) {
|
|
71
|
+
container = container.withReuse()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const startedContainer = await container.start()
|
|
75
|
+
|
|
76
|
+
const info = testContainerUtils.getContainerById(startedContainer.getId())
|
|
77
|
+
if (!info) {
|
|
78
|
+
throw new Error("Container not found")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Some Docker runtimes, when you expose a port, will bind it to both
|
|
82
|
+
// 127.0.0.1 and ::1, so ipv4 and ipv6. The port spaces of ipv4 and ipv6
|
|
83
|
+
// addresses are not shared, and testcontainers will sometimes give you back
|
|
84
|
+
// the ipv6 port. There's no way to know that this has happened, and if you
|
|
85
|
+
// try to then connect to `localhost:port` you may attempt to bind to the v4
|
|
86
|
+
// address which could be unbound or even an entirely different container. For
|
|
87
|
+
// that reason, we don't use testcontainers' `getExposedPort` function,
|
|
88
|
+
// preferring instead our own method that guaranteed v4 ports.
|
|
89
|
+
return testContainerUtils.getExposedV4Ports(info)
|
|
25
90
|
}
|