params.test.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. // Copyright Materialize, Inc. and contributors. All rights reserved.
  2. //
  3. // Use of this software is governed by the Business Source License
  4. // included in the LICENSE file.
  5. //
  6. // As of the Change Date specified in that file, in accordance with
  7. // the Business Source License, use of this software will be governed
  8. // by the Apache License, Version 2.0.
  9. import { Client } from "pg";
  10. import QueryStream = require("pg-query-stream");
  11. const { pipeline } = require("node:stream/promises");
  12. const client = new Client({
  13. port: parseInt(process.env.PGPORT, 10) || 6875,
  14. database: process.env.PGDATABASE || "materialize",
  15. user: process.env.PGUSER || "materialize",
  16. });
  17. beforeAll(async () => await client.connect());
  18. afterAll(async () => await client.end());
  19. describe("query api", () => {
  20. const bindError = ({ expected, actual, name = "" }) =>
  21. expect.objectContaining({
  22. code: "08P01",
  23. message:
  24. `bind message supplies ${actual} parameters, ` +
  25. `but prepared statement "${name}" requires ${expected}`,
  26. });
  27. it("should reject invalid queries with too few parameters", async () => {
  28. await expect(client.query("SELECT $1 || $2", ["1"])).rejects.toThrow(
  29. bindError({ expected: 2, actual: 1 }),
  30. );
  31. });
  32. it("should reject invalid queries with too many parameters", async () => {
  33. await expect(client.query("SELECT 1", ["1", "2"])).rejects.toThrow(
  34. bindError({ expected: 0, actual: 2 }),
  35. );
  36. });
  37. it("should include the prepared statement name in the error message", async () => {
  38. await expect(
  39. client.query({ text: "SELECT $1", name: "foo" }),
  40. ).rejects.toThrow(bindError({ expected: 1, actual: 0, name: "foo" }));
  41. });
  42. it("should allow queries with the correct number of parameters", async () => {
  43. const res = await client.query({
  44. text: "SELECT $1 || $2",
  45. values: ["1", "2"],
  46. rowMode: "array",
  47. });
  48. expect(res.rows).toEqual([["12"]]);
  49. });
  50. describe("list parameters", () => {
  51. it("should handle a simple int array", async () => {
  52. const res = await client.query({
  53. text: "SELECT $1::int list",
  54. values: ["{ 1, NULL, 2}"],
  55. rowMode: "array",
  56. });
  57. expect(res.rows).toEqual([["{1,NULL,2}"]]);
  58. });
  59. it("should handle a nested text array", async () => {
  60. const res = await client.query({
  61. text: "SELECT $1::text list list",
  62. values: [`{ { }, "{}", {a, "", "\\""}, "{a,\\"\\",\\"\\\\\\"\\"}"}`],
  63. rowMode: "array",
  64. });
  65. expect(res.rows).toEqual([[`{{},{},{a,"","\\""},{a,"","\\""}}`]]);
  66. });
  67. it("should reject mismatched types", async () => {
  68. await expect(
  69. client.query({
  70. text: "SELECT $1::int list",
  71. values: [`{a}`],
  72. }),
  73. ).rejects.toThrow(
  74. expect.objectContaining({
  75. code: "22023",
  76. message: expect.stringMatching(
  77. /unable to decode parameter:.*invalid digit found in string/,
  78. ),
  79. }),
  80. );
  81. });
  82. });
  83. });
  84. describe("query stream api", () => {
  85. it("should work for result sets that fit in a single batch", async () => {
  86. const queryStream = new QueryStream("SELECT generate_series(1, 2) v", [], {
  87. batchSize: 3,
  88. });
  89. const rows = [];
  90. for await (const row of client.query(queryStream)) {
  91. rows.push(row);
  92. }
  93. expect(rows).toEqual([{ v: 1 }, { v: 2 }]);
  94. });
  95. it("should work for result sets that require multiple batches", async () => {
  96. const queryStream = new QueryStream("SELECT generate_series(1, 6) v", [], {
  97. batchSize: 3,
  98. });
  99. const rows = [];
  100. for await (const row of client.query(queryStream)) {
  101. rows.push(row);
  102. }
  103. expect(rows).toEqual([
  104. { v: 1 },
  105. { v: 2 },
  106. { v: 3 },
  107. { v: 4 },
  108. { v: 5 },
  109. { v: 6 },
  110. ]);
  111. });
  112. it("should throw errors on invalid queries", async () => {
  113. const queryStream = new QueryStream("ELECT 1");
  114. const fetchData = async () => {
  115. const rows = [];
  116. for await (const row of client.query(queryStream)) {
  117. rows.push(row);
  118. }
  119. };
  120. await expect(fetchData).rejects.toThrow(
  121. "Expected a keyword at the beginning of a statement",
  122. );
  123. });
  124. });