param_types.go 16 KB

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