2
0

param_types.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. package pgserver
  2. import (
  3. "fmt"
  4. "strconv"
  5. "strings"
  6. "github.com/danfragoso/pizzasql-next/pkg/lexer"
  7. "github.com/danfragoso/pizzasql-next/pkg/parser"
  8. "github.com/danfragoso/pizzasql-next/pkg/storage"
  9. )
  10. // textParameterOID is the type a parameter falls back to when the SQL gives no
  11. // usable context. PostgreSQL's Describe never reports OID 0 for a parameter:
  12. // drivers such as pgx reject the statement client-side when the described
  13. // parameter count or type disagrees with the arguments they were given.
  14. const textParameterOID = 25
  15. // markerPrefix identifies the string literal substituted for a $n placeholder
  16. // while a statement is parsed for type inference. The parser has no parameter
  17. // node, so each placeholder becomes a uniquely named string literal whose
  18. // position in the AST supplies the surrounding comparison or assignment
  19. // context.
  20. const markerPrefix = "__pizzasql_parameter_"
  21. // maxParameterNumber is PostgreSQL's protocol limit on parameter numbers. A
  22. // Parse message addresses parameters by number and ParameterDescription counts
  23. // them in a 16-bit field, so a larger placeholder can never be described; it is
  24. // rejected before any allocation sized from the query text.
  25. const maxParameterNumber = 65535
  26. // parameterReference records one $n placeholder found by the quote- and
  27. // comment-aware scan that Bind uses. number is clamped to maxParameterNumber+1
  28. // when the digits exceed the protocol limit, and stays 0 for $0; Bind reports
  29. // both as a missing parameter.
  30. type parameterReference struct {
  31. number int
  32. start int
  33. end int
  34. }
  35. // negotiateParameterOIDs builds the ParameterDescription type list for query.
  36. // The list length is the highest placeholder number (or the client's declared
  37. // count when that is larger), client-declared non-zero OIDs win, and every
  38. // remaining entry is inferred from the SQL or falls back to TEXT. A placeholder
  39. // number above the protocol limit is an error, so a hostile query can never
  40. // size the list.
  41. func (c *Connection) negotiateParameterOIDs(query string, declared []int32) ([]int32, error) {
  42. references := scanParameters(query)
  43. count := len(declared)
  44. for _, reference := range references {
  45. if reference.number > maxParameterNumber {
  46. return nil, fmt.Errorf("parameter %s exceeds PostgreSQL's limit of %d parameters", query[reference.start:reference.end], maxParameterNumber)
  47. }
  48. if reference.number > count {
  49. count = reference.number
  50. }
  51. }
  52. if count > maxParameterNumber {
  53. return nil, fmt.Errorf("statement declares %d parameters; PostgreSQL's limit is %d", count, maxParameterNumber)
  54. }
  55. oids := make([]int32, count)
  56. copy(oids, declared)
  57. if count == 0 {
  58. return oids, nil
  59. }
  60. inferred := c.inferParameterTypes(query, references)
  61. for i := range oids {
  62. if oids[i] != 0 {
  63. continue
  64. }
  65. oids[i] = textParameterOID
  66. if typeName, ok := inferred[i+1]; ok {
  67. oids[i] = c.getOIDForType(typeName)
  68. }
  69. }
  70. return oids, nil
  71. }
  72. // inferParameterTypes returns the type name of every parameter whose SQL
  73. // context names one. Inference is best effort: a statement the parser does not
  74. // accept, or a column the catalog cannot resolve, leaves that parameter out so
  75. // the caller falls back to TEXT.
  76. func (c *Connection) inferParameterTypes(query string, references []parameterReference) map[int]string {
  77. if len(references) == 0 {
  78. return nil
  79. }
  80. marked, markers := substituteParameterMarkers(query, references)
  81. stmt, err := parser.New(lexer.New(marked)).Parse()
  82. if err != nil {
  83. return nil
  84. }
  85. inferrer := &parameterInferrer{
  86. conn: c,
  87. markers: markers,
  88. types: make(map[int]string),
  89. }
  90. inferrer.walkStatement(nil, stmt)
  91. return inferrer.types
  92. }
  93. // scanParameters locates $n placeholders outside string literals, quoted
  94. // identifiers, and comments. Bind and parameter inference share it, so Describe
  95. // sees exactly the placeholders Bind replaces.
  96. func scanParameters(query string) []parameterReference {
  97. var references []parameterReference
  98. var quote byte
  99. for i := 0; i < len(query); {
  100. ch := query[i]
  101. if quote != 0 {
  102. i++
  103. if ch == quote {
  104. if i < len(query) && query[i] == quote {
  105. i++
  106. } else {
  107. quote = 0
  108. }
  109. }
  110. continue
  111. }
  112. if i+1 < len(query) && ch == '-' && query[i+1] == '-' {
  113. end := strings.IndexByte(query[i:], '\n')
  114. if end < 0 {
  115. break
  116. }
  117. i += end + 1
  118. continue
  119. }
  120. if i+1 < len(query) && ch == '/' && query[i+1] == '*' {
  121. end := strings.Index(query[i+2:], "*/")
  122. if end < 0 {
  123. break
  124. }
  125. i += end + 4
  126. continue
  127. }
  128. // Brackets are ordinary syntax: a subscript such as tags[$1] binds $1.
  129. if ch == '\'' || ch == '"' || ch == '`' {
  130. quote = ch
  131. i++
  132. continue
  133. }
  134. if ch == '$' && i+1 < len(query) && query[i+1] >= '0' && query[i+1] <= '9' {
  135. end := i + 1
  136. for end < len(query) && query[end] >= '0' && query[end] <= '9' {
  137. end++
  138. }
  139. // Clamp at the protocol limit instead of accumulating every digit:
  140. // a hostile $999... must not overflow or size an allocation.
  141. number := 0
  142. for _, digit := range query[i+1 : end] {
  143. if number > maxParameterNumber {
  144. break
  145. }
  146. number = number*10 + int(digit-'0')
  147. if number > maxParameterNumber {
  148. number = maxParameterNumber + 1
  149. }
  150. }
  151. references = append(references, parameterReference{number: number, start: i, end: end})
  152. i = end
  153. continue
  154. }
  155. i++
  156. }
  157. return references
  158. }
  159. // substituteParameterMarkers replaces each placeholder with a distinct string
  160. // literal and reports marker text -> parameter number.
  161. func substituteParameterMarkers(query string, references []parameterReference) (string, map[string]int) {
  162. var marked strings.Builder
  163. markers := make(map[string]int, len(references))
  164. last := 0
  165. for i, reference := range references {
  166. marker := markerPrefix + strconv.Itoa(i) + "__"
  167. markers[marker] = reference.number
  168. marked.WriteString(query[last:reference.start])
  169. marked.WriteString("'")
  170. marked.WriteString(marker)
  171. marked.WriteString("'")
  172. last = reference.end
  173. }
  174. marked.WriteString(query[last:])
  175. return marked.String(), markers
  176. }
  177. // parameterInferrer walks one parsed statement and records the type name each
  178. // parameter occurrence is compared with or assigned to. The first resolved
  179. // occurrence of a parameter wins.
  180. type parameterInferrer struct {
  181. conn *Connection
  182. markers map[string]int
  183. types map[int]string
  184. }
  185. func (in *parameterInferrer) note(marker, typeName string) {
  186. number, ok := in.markers[marker]
  187. if !ok || typeName == "" {
  188. return
  189. }
  190. if _, recorded := in.types[number]; recorded {
  191. return
  192. }
  193. in.types[number] = typeName
  194. }
  195. // parameterScope is one query level's table set. Column types come from the
  196. // storage catalog through the connection's session, so DDL staged by the
  197. // current transaction is visible.
  198. type parameterScope struct {
  199. parent *parameterScope
  200. tables []parameterTable
  201. }
  202. type parameterTable struct {
  203. name string
  204. alias string
  205. schema *storage.Schema
  206. }
  207. func (c *Connection) parameterTableSchema(name string) *storage.Schema {
  208. if name == "" {
  209. return nil
  210. }
  211. if c.executor != nil {
  212. if schema, err := c.executor.Session().GetSchema(name); err == nil {
  213. return schema
  214. }
  215. return nil
  216. }
  217. if c.schema != nil {
  218. if schema, err := c.schema.GetSchema(name); err == nil {
  219. return schema
  220. }
  221. }
  222. return nil
  223. }
  224. func (c *Connection) newParameterScope(parent *parameterScope, refs []parser.TableRef) *parameterScope {
  225. scope := &parameterScope{parent: parent}
  226. for _, ref := range refs {
  227. scope.addTable(c, ref)
  228. }
  229. return scope
  230. }
  231. func (s *parameterScope) addTable(c *Connection, ref parser.TableRef) {
  232. if ref.Subquery == nil && ref.Function == nil {
  233. if schema := c.parameterTableSchema(ref.Name); schema != nil {
  234. s.tables = append(s.tables, parameterTable{name: ref.Name, alias: ref.Alias, schema: schema})
  235. }
  236. }
  237. if ref.Join != nil && ref.Join.Table != nil {
  238. s.addTable(c, *ref.Join.Table)
  239. }
  240. }
  241. // columnType resolves a column reference to its catalog type name. An
  242. // unqualified name is only used when exactly one table in the level has it; a
  243. // qualified name that names a table without the column stops the search rather
  244. // than falling through to an outer query.
  245. func (s *parameterScope) columnType(ref *parser.ColumnRef) (string, bool) {
  246. for scope := s; scope != nil; scope = scope.parent {
  247. var found string
  248. matches, matchedTable := 0, false
  249. for _, table := range scope.tables {
  250. if ref.Table != "" && !strings.EqualFold(table.name, ref.Table) && !strings.EqualFold(table.alias, ref.Table) {
  251. continue
  252. }
  253. matchedTable = true
  254. for _, column := range table.schema.Columns {
  255. if !strings.EqualFold(column.Name, ref.Column) {
  256. continue
  257. }
  258. matches++
  259. found = column.Type
  260. }
  261. }
  262. if matches > 0 {
  263. return found, matches == 1
  264. }
  265. if ref.Table != "" && matchedTable {
  266. return "", false
  267. }
  268. }
  269. return "", false
  270. }
  271. func tableRefName(ref *parser.TableRef) string {
  272. if ref == nil {
  273. return ""
  274. }
  275. return ref.Name
  276. }
  277. func schemaColumnType(schema *storage.Schema, name string) string {
  278. if schema == nil {
  279. return ""
  280. }
  281. for _, column := range schema.Columns {
  282. if strings.EqualFold(column.Name, name) {
  283. return column.Type
  284. }
  285. }
  286. return ""
  287. }
  288. func (in *parameterInferrer) walkStatement(scope *parameterScope, stmt parser.Statement) {
  289. switch s := stmt.(type) {
  290. case *parser.SelectStmt:
  291. in.walkSelect(scope, s)
  292. case *parser.CompoundSelect:
  293. in.walkSelect(scope, s.Left)
  294. in.walkSelect(scope, s.Right)
  295. in.walkExpr(scope, s.Limit, "BIGINT")
  296. in.walkExpr(scope, s.Offset, "BIGINT")
  297. case *parser.InsertStmt:
  298. in.walkInsert(scope, s)
  299. case *parser.UpdateStmt:
  300. in.walkUpdate(scope, s)
  301. case *parser.DeleteStmt:
  302. in.walkDelete(scope, s)
  303. }
  304. }
  305. // walkTableRefs visits the bodies and join conditions of a FROM list. The scope
  306. // already contains every table in the query level, so a join condition resolves
  307. // columns from both sides.
  308. func (in *parameterInferrer) walkTableRefs(scope *parameterScope, refs []parser.TableRef) {
  309. for _, ref := range refs {
  310. in.walkTableRef(scope, ref)
  311. }
  312. }
  313. func (in *parameterInferrer) walkTableRef(scope *parameterScope, ref parser.TableRef) {
  314. if ref.Subquery != nil {
  315. in.walkSelect(scope, ref.Subquery)
  316. }
  317. if ref.Function != nil {
  318. for _, arg := range ref.Function.Args {
  319. in.walkExpr(scope, arg, "")
  320. }
  321. }
  322. if ref.Join != nil {
  323. if ref.Join.Table != nil {
  324. in.walkTableRef(scope, *ref.Join.Table)
  325. }
  326. in.walkExpr(scope, ref.Join.Condition, "")
  327. }
  328. }
  329. func (in *parameterInferrer) walkSelect(parent *parameterScope, stmt *parser.SelectStmt) {
  330. if stmt == nil {
  331. return
  332. }
  333. for _, cte := range stmt.With {
  334. in.walkSelect(parent, cte.Query)
  335. }
  336. scope := in.conn.newParameterScope(parent, stmt.From)
  337. in.walkTableRefs(scope, stmt.From)
  338. for _, column := range stmt.Columns {
  339. in.walkExpr(scope, column.Expr, "")
  340. }
  341. in.walkExpr(scope, stmt.Where, "")
  342. for _, expr := range stmt.GroupBy {
  343. in.walkExpr(scope, expr, "")
  344. }
  345. in.walkExpr(scope, stmt.Having, "")
  346. for _, item := range stmt.OrderBy {
  347. in.walkExpr(scope, item.Expr, "")
  348. }
  349. in.walkExpr(scope, stmt.Limit, "BIGINT")
  350. in.walkExpr(scope, stmt.Offset, "BIGINT")
  351. if stmt.Compound != nil {
  352. in.walkStatement(parent, stmt.Compound)
  353. }
  354. }
  355. func (in *parameterInferrer) walkInsert(parent *parameterScope, stmt *parser.InsertStmt) {
  356. var target []parser.TableRef
  357. if stmt.Table != nil {
  358. target = append(target, *stmt.Table)
  359. }
  360. schema := in.conn.parameterTableSchema(tableRefName(stmt.Table))
  361. scope := in.conn.newParameterScope(parent, target)
  362. // Without an explicit column list, VALUES positions map to the table's
  363. // columns in declaration order.
  364. columnTypes := make([]string, 0, len(stmt.Columns))
  365. if len(stmt.Columns) == 0 {
  366. if schema != nil {
  367. for _, column := range schema.Columns {
  368. // Generated columns are not writable and are skipped when
  369. // positional values map to the table's columns.
  370. if column.GeneratedExpr != "" {
  371. continue
  372. }
  373. columnTypes = append(columnTypes, column.Type)
  374. }
  375. }
  376. } else {
  377. for _, name := range stmt.Columns {
  378. columnTypes = append(columnTypes, schemaColumnType(schema, name))
  379. }
  380. }
  381. for _, row := range stmt.Values {
  382. for i, value := range row {
  383. expected := ""
  384. if i < len(columnTypes) {
  385. expected = columnTypes[i]
  386. }
  387. in.walkExpr(scope, value, expected)
  388. }
  389. }
  390. in.walkSelect(parent, stmt.Select)
  391. for _, assignment := range stmt.ConflictUpdate {
  392. in.walkExpr(scope, assignment.Value, schemaColumnType(schema, assignment.Column))
  393. }
  394. for _, column := range stmt.Returning {
  395. in.walkExpr(scope, column.Expr, "")
  396. }
  397. }
  398. func (in *parameterInferrer) walkUpdate(parent *parameterScope, stmt *parser.UpdateStmt) {
  399. var target []parser.TableRef
  400. if stmt.Table != nil {
  401. target = append(target, *stmt.Table)
  402. }
  403. schema := in.conn.parameterTableSchema(tableRefName(stmt.Table))
  404. scope := in.conn.newParameterScope(parent, append(target, stmt.From...))
  405. in.walkTableRefs(scope, stmt.From)
  406. for _, assignment := range stmt.Set {
  407. in.walkExpr(scope, assignment.Value, schemaColumnType(schema, assignment.Column))
  408. }
  409. in.walkExpr(scope, stmt.Where, "")
  410. for _, column := range stmt.Returning {
  411. in.walkExpr(scope, column.Expr, "")
  412. }
  413. }
  414. func (in *parameterInferrer) walkDelete(parent *parameterScope, stmt *parser.DeleteStmt) {
  415. var target []parser.TableRef
  416. if stmt.Table != nil {
  417. target = append(target, *stmt.Table)
  418. }
  419. scope := in.conn.newParameterScope(parent, target)
  420. in.walkExpr(scope, stmt.Where, "")
  421. for _, column := range stmt.Returning {
  422. in.walkExpr(scope, column.Expr, "")
  423. }
  424. }
  425. // walkExpr visits expr with the type expected from its surrounding context.
  426. // Only the operands of comparison and containment operators, assignment
  427. // targets, and ANY/ALL operands supply a context; everything else recurses
  428. // without one.
  429. func (in *parameterInferrer) walkExpr(scope *parameterScope, expr parser.Expr, expected string) {
  430. switch e := expr.(type) {
  431. case nil:
  432. return
  433. case *parser.ParenExpr:
  434. in.walkExpr(scope, e.Expr, expected)
  435. case *parser.LiteralExpr:
  436. if e.Type == lexer.TokenString {
  437. in.note(e.Value, expected)
  438. }
  439. case *parser.CastExpr:
  440. if literal, ok := markerLiteral(e.Expr); ok {
  441. in.note(literal, e.Type.SQLTypeName())
  442. return
  443. }
  444. in.walkExpr(scope, e.Expr, "")
  445. case *parser.BinaryExpr:
  446. if isParameterTypeOperator(e.Op) {
  447. leftType, leftOK := in.exprType(scope, e.Left)
  448. rightType, rightOK := in.exprType(scope, e.Right)
  449. if leftOK {
  450. in.walkExpr(scope, e.Right, leftType)
  451. } else {
  452. in.walkExpr(scope, e.Right, "")
  453. }
  454. if rightOK {
  455. in.walkExpr(scope, e.Left, rightType)
  456. } else {
  457. in.walkExpr(scope, e.Left, "")
  458. }
  459. return
  460. }
  461. in.walkExpr(scope, e.Left, "")
  462. in.walkExpr(scope, e.Right, "")
  463. case *parser.AnyAllExpr:
  464. if columnType, ok := in.exprType(scope, e.Left); ok {
  465. in.walkExpr(scope, e.Array, parameterArrayType(columnType))
  466. } else {
  467. in.walkExpr(scope, e.Array, "")
  468. }
  469. in.walkExpr(scope, e.Left, "")
  470. case *parser.InExpr:
  471. columnType, ok := in.exprType(scope, e.Left)
  472. for _, value := range e.Values {
  473. if ok {
  474. in.walkExpr(scope, value, columnType)
  475. } else {
  476. in.walkExpr(scope, value, "")
  477. }
  478. }
  479. in.walkExpr(scope, e.Left, "")
  480. in.walkSelect(scope, e.Subquery)
  481. case *parser.BetweenExpr:
  482. if columnType, ok := in.exprType(scope, e.Left); ok {
  483. in.walkExpr(scope, e.Low, columnType)
  484. in.walkExpr(scope, e.High, columnType)
  485. } else {
  486. in.walkExpr(scope, e.Low, "")
  487. in.walkExpr(scope, e.High, "")
  488. }
  489. in.walkExpr(scope, e.Left, "")
  490. case *parser.IsDistinctExpr:
  491. leftType, leftOK := in.exprType(scope, e.Left)
  492. rightType, rightOK := in.exprType(scope, e.Right)
  493. if leftOK {
  494. in.walkExpr(scope, e.Right, leftType)
  495. } else {
  496. in.walkExpr(scope, e.Right, "")
  497. }
  498. if rightOK {
  499. in.walkExpr(scope, e.Left, rightType)
  500. } else {
  501. in.walkExpr(scope, e.Left, "")
  502. }
  503. case *parser.IsNullExpr:
  504. in.walkExpr(scope, e.Left, "")
  505. case *parser.LikeExpr:
  506. in.walkExpr(scope, e.Left, "")
  507. in.walkExpr(scope, e.Pattern, "TEXT")
  508. in.walkExpr(scope, e.Escape, "TEXT")
  509. case *parser.UnaryExpr:
  510. in.walkExpr(scope, e.Operand, "")
  511. case *parser.FunctionCall:
  512. for _, arg := range e.Args {
  513. in.walkExpr(scope, arg, "")
  514. }
  515. case *parser.WindowExpr:
  516. if e.Func != nil {
  517. for _, arg := range e.Func.Args {
  518. in.walkExpr(scope, arg, "")
  519. }
  520. }
  521. for _, expr := range e.PartitionBy {
  522. in.walkExpr(scope, expr, "")
  523. }
  524. for _, item := range e.OrderBy {
  525. in.walkExpr(scope, item.Expr, "")
  526. }
  527. case *parser.CaseExpr:
  528. in.walkExpr(scope, e.Operand, "")
  529. for _, when := range e.Whens {
  530. in.walkExpr(scope, when.Condition, "")
  531. in.walkExpr(scope, when.Result, expected)
  532. }
  533. in.walkExpr(scope, e.Else, expected)
  534. case *parser.ArrayExpr:
  535. for _, element := range e.Elements {
  536. in.walkExpr(scope, element, "")
  537. }
  538. case *parser.SubscriptExpr:
  539. in.walkExpr(scope, e.Expr, "")
  540. // An array subscript is an integer position regardless of the array's
  541. // element type.
  542. in.walkExpr(scope, e.Index, "INTEGER")
  543. case *parser.SubqueryExpr:
  544. in.walkSelect(scope, e.Query)
  545. case *parser.ExistsExpr:
  546. in.walkSelect(scope, e.Subquery)
  547. }
  548. }
  549. // exprType reports the type of an expression that names one directly: a column
  550. // from the catalog or an explicit cast.
  551. func (in *parameterInferrer) exprType(scope *parameterScope, expr parser.Expr) (string, bool) {
  552. switch e := expr.(type) {
  553. case *parser.ParenExpr:
  554. return in.exprType(scope, e.Expr)
  555. case *parser.ColumnRef:
  556. if scope == nil {
  557. return "", false
  558. }
  559. return scope.columnType(e)
  560. case *parser.CastExpr:
  561. return e.Type.SQLTypeName(), true
  562. case *parser.SubscriptExpr:
  563. // An array subscript has the column's element type, so
  564. // `nums[1] = $1` infers INTEGER from an INTEGER[] column.
  565. if baseType, ok := in.exprType(scope, e.Expr); ok {
  566. if element, isArray := arrayElementTypeName(baseType); isArray {
  567. return element, true
  568. }
  569. }
  570. }
  571. return "", false
  572. }
  573. // markerLiteral reports the parameter marker text when expr is a marker,
  574. // looking through parentheses.
  575. func markerLiteral(expr parser.Expr) (string, bool) {
  576. for {
  577. paren, ok := expr.(*parser.ParenExpr)
  578. if !ok {
  579. break
  580. }
  581. expr = paren.Expr
  582. }
  583. literal, ok := expr.(*parser.LiteralExpr)
  584. if !ok || literal.Type != lexer.TokenString || !strings.HasPrefix(literal.Value, markerPrefix) {
  585. return "", false
  586. }
  587. return literal.Value, true
  588. }
  589. // isParameterTypeOperator reports whether an operator's operands share a type,
  590. // so one side can supply the other side's parameter type.
  591. func isParameterTypeOperator(op lexer.TokenType) bool {
  592. switch op {
  593. case lexer.TokenEq, lexer.TokenNeq, lexer.TokenLt, lexer.TokenLte,
  594. lexer.TokenGt, lexer.TokenGte, lexer.TokenAtArrow, lexer.TokenLtAt:
  595. return true
  596. }
  597. return false
  598. }
  599. // parameterArrayType returns the array type of a column's element type. An
  600. // array column keeps its declared array type: ANY/ALL over an array column is
  601. // the engine's supported form.
  602. func parameterArrayType(columnType string) string {
  603. if _, isArray := arrayElementTypeName(columnType); isArray {
  604. return columnType
  605. }
  606. return columnType + "[]"
  607. }