├── .gitignore ├── LICENSE ├── README.md ├── cmd └── sqlfmt │ ├── main.go │ └── sqlfmt_test.go ├── integration_test.go ├── kwlist.go ├── lex.go ├── parsed_types.go ├── renderer.go ├── renderer_test.go ├── sql.go ├── sql.y ├── testdata ├── arithmetic_expression.golden.sql ├── arithmetic_expression.input.sql ├── array_constructor.golden.sql ├── array_constructor.input.sql ├── array_index.golden.sql ├── array_index.input.sql ├── array_slice.golden.sql ├── array_slice.input.sql ├── array_subselect.golden.sql ├── array_subselect.input.sql ├── array_typecast.golden.sql ├── array_typecast.input.sql ├── at_time_zone.golden.sql ├── at_time_zone.input.sql ├── b_expr.golden.sql ├── b_expr.input.sql ├── between.golden.sql ├── between.input.sql ├── bitconst.golden.sql ├── bitconst.input.sql ├── boolean_binary_op.golden.sql ├── boolean_binary_op.input.sql ├── boolean_not.golden.sql ├── boolean_not.input.sql ├── case_full.golden.sql ├── case_full.input.sql ├── case_implicit.golden.sql ├── case_implicit.input.sql ├── cast_as.golden.sql ├── cast_as.input.sql ├── collate.golden.sql ├── collate.input.sql ├── collation_for.golden.sql ├── collation_for.input.sql ├── comment_beginning_single_line.golden.sql ├── comment_beginning_single_line.input.sql ├── comparison_expression.golden.sql ├── comparison_expression.input.sql ├── const_type_name.golden.sql ├── const_type_name.input.sql ├── custom_operators.golden.sql ├── custom_operators.input.sql ├── distinct.golden.sql ├── distinct.input.sql ├── distinct_on.golden.sql ├── distinct_on.input.sql ├── except.golden.sql ├── except.input.sql ├── exists.golden.sql ├── exists.input.sql ├── extract.golden.sql ├── extract.input.sql ├── float_constant.golden.sql ├── float_constant.input.sql ├── func_expr_expr_list.golden.sql ├── func_expr_expr_list.input.sql ├── func_expr_no_parens.golden.sql ├── func_expr_no_parens.input.sql ├── func_expr_one_arg.golden.sql ├── func_expr_one_arg.input.sql ├── function_call_qualified.golden.sql ├── function_call_qualified.input.sql ├── function_call_variadic.golden.sql ├── function_call_variadic.input.sql ├── function_call_with_all.golden.sql ├── function_call_with_all.input.sql ├── function_call_with_distinct.golden.sql ├── function_call_with_distinct.input.sql ├── function_call_with_filter.golden.sql ├── function_call_with_filter.input.sql ├── function_call_with_grouping_set.golden.sql ├── function_call_with_grouping_set.input.sql ├── function_call_with_order.golden.sql ├── function_call_with_order.input.sql ├── function_call_with_pg_named_args.golden.sql ├── function_call_with_pg_named_args.input.sql ├── function_call_with_positional_args.golden.sql ├── function_call_with_positional_args.input.sql ├── function_call_with_sql_named_args.golden.sql ├── function_call_with_sql_named_args.input.sql ├── function_call_with_star_arg.golden.sql ├── function_call_with_star_arg.input.sql ├── function_call_without_args.golden.sql ├── function_call_without_args.input.sql ├── group_by.golden.sql ├── group_by.input.sql ├── having.golden.sql ├── having.input.sql ├── in.golden.sql ├── in.input.sql ├── intersect.golden.sql ├── intersect.input.sql ├── interval.golden.sql ├── interval.input.sql ├── is_bool_op.golden.sql ├── is_bool_op.input.sql ├── is_distinct_from.golden.sql ├── is_distinct_from.input.sql ├── is_document.golden.sql ├── is_document.input.sql ├── is_null.golden.sql ├── is_null.input.sql ├── is_of_type_list.golden.sql ├── is_of_type_list.input.sql ├── like.golden.sql ├── like.input.sql ├── limit.golden.sql ├── limit.input.sql ├── limit_fetch.golden.sql ├── limit_fetch.input.sql ├── limit_offset.golden.sql ├── limit_offset.input.sql ├── null.golden.sql ├── null.input.sql ├── nullif.golden.sql ├── nullif.input.sql ├── offset.golden.sql ├── offset.input.sql ├── offset_fetch.golden.sql ├── offset_fetch.input.sql ├── offset_limit.golden.sql ├── offset_limit.input.sql ├── order.golden.sql ├── order.input.sql ├── order_column_num.golden.sql ├── order_column_num.input.sql ├── order_desc.golden.sql ├── order_desc.input.sql ├── order_multiple.golden.sql ├── order_multiple.input.sql ├── order_nulls.golden.sql ├── order_nulls.input.sql ├── order_using.golden.sql ├── order_using.input.sql ├── overlaps.golden.sql ├── overlaps.input.sql ├── overlay.golden.sql ├── overlay.input.sql ├── paren_expression.golden.sql ├── paren_expression.input.sql ├── position.golden.sql ├── position.input.sql ├── postfix_operator.golden.sql ├── postfix_operator.input.sql ├── quoted_identifier.golden.sql ├── quoted_identifier.input.sql ├── row.golden.sql ├── row.input.sql ├── select_for_key_share.golden.sql ├── select_for_key_share.input.sql ├── select_for_no_key_update.golden.sql ├── select_for_no_key_update.input.sql ├── select_for_share.golden.sql ├── select_for_share.input.sql ├── select_for_update.golden.sql ├── select_for_update.input.sql ├── select_for_update_nowait.golden.sql ├── select_for_update_nowait.input.sql ├── select_for_update_of.golden.sql ├── select_for_update_of.input.sql ├── select_from_aliased.golden.sql ├── select_from_aliased.input.sql ├── select_from_comma_join.golden.sql ├── select_from_comma_join.input.sql ├── select_from_cross_join.golden.sql ├── select_from_cross_join.input.sql ├── select_from_join_on.golden.sql ├── select_from_join_on.input.sql ├── select_from_join_using.golden.sql ├── select_from_join_using.input.sql ├── select_from_join_using_multiple.golden.sql ├── select_from_join_using_multiple.input.sql ├── select_from_left_join_on.golden.sql ├── select_from_left_join_on.input.sql ├── select_from_natural_join.golden.sql ├── select_from_natural_join.input.sql ├── select_into.golden.sql ├── select_into.input.sql ├── select_star.golden.sql ├── select_star.input.sql ├── select_table_dot_column.golden.sql ├── select_table_dot_column.input.sql ├── select_table_dot_star.golden.sql ├── select_table_dot_star.input.sql ├── select_where.golden.sql ├── select_where.input.sql ├── select_wrapped_by_parens.golden.sql ├── select_wrapped_by_parens.input.sql ├── semicolon.golden.sql ├── semicolon.input.sql ├── simple_select_literal_integer.golden.sql ├── simple_select_literal_integer.input.sql ├── simple_select_literal_text.golden.sql ├── simple_select_literal_text.input.sql ├── simple_select_with_from.golden.sql ├── simple_select_with_from.input.sql ├── simple_select_with_selection_alias.golden.sql ├── simple_select_with_selection_alias.input.sql ├── simple_select_with_selection_alias_no_as.golden.sql ├── simple_select_with_selection_alias_no_as.input.sql ├── simple_select_without_from.golden.sql ├── simple_select_without_from.input.sql ├── subquery_op.golden.sql ├── subquery_op.input.sql ├── subselect_expression.golden.sql ├── subselect_expression.input.sql ├── substring.golden.sql ├── substring.input.sql ├── table.golden.sql ├── table.input.sql ├── table_only.golden.sql ├── table_only.input.sql ├── table_only_paren.golden.sql ├── table_only_paren.input.sql ├── table_qualified.golden.sql ├── table_qualified.input.sql ├── table_star.golden.sql ├── table_star.input.sql ├── treat.golden.sql ├── treat.input.sql ├── trim.golden.sql ├── trim.input.sql ├── typecast.golden.sql ├── typecast.input.sql ├── unary.golden.sql ├── unary.input.sql ├── union.golden.sql ├── union.input.sql ├── values.golden.sql ├── values.input.sql ├── values_default.golden.sql ├── values_default.input.sql ├── values_order.golden.sql ├── values_order.input.sql ├── window_function.golden.sql ├── window_function.input.sql ├── window_function_frame.golden.sql ├── window_function_frame.input.sql ├── window_function_named.golden.sql ├── window_function_named.input.sql ├── window_function_named_multiple.golden.sql ├── window_function_named_multiple.input.sql ├── window_function_order_by.golden.sql ├── window_function_order_by.input.sql ├── window_function_partition_by.golden.sql ├── window_function_partition_by.input.sql ├── xmlelement.golden.sql ├── xmlelement.input.sql ├── xmlexists.golden.sql ├── xmlexists.input.sql ├── xmlforest.golden.sql ├── xmlforest.input.sql ├── xmlparse.golden.sql ├── xmlparse.input.sql ├── xmlpi.golden.sql ├── xmlpi.input.sql ├── xmlroot.golden.sql ├── xmlroot.input.sql ├── xmlserialize.golden.sql └── xmlserialize.input.sql ├── token_renderer.go └── token_renderer_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | /cmd/sqlfmt/sqlfmt 2 | /cmd/sqlfmt/tmp 3 | /y.output 4 | /tmp 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 Jack Christensen 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "{}" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright {yyyy} {name of copyright owner} 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | 205 | ------------------------------------------------------------------------------- 206 | 207 | Contains code derived from PostgreSQL. 208 | 209 | PostgreSQL Database Management System 210 | (formerly known as Postgres, then as Postgres95) 211 | 212 | Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group 213 | 214 | Portions Copyright (c) 1994, The Regents of the University of California 215 | 216 | Permission to use, copy, modify, and distribute this software and its 217 | documentation for any purpose, without fee, and without a written agreement 218 | is hereby granted, provided that the above copyright notice and this 219 | paragraph and the following two paragraphs appear in all copies. 220 | 221 | IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR 222 | DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING 223 | LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS 224 | DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE 225 | POSSIBILITY OF SUCH DAMAGE. 226 | 227 | THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 228 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 229 | AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS 230 | ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO 231 | PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 232 | 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sqlfmt 2 | 3 | ## Installation 4 | 5 | ```sh 6 | $ go get github.com/jackc/sqlfmt/... 7 | $ which sqlfmt 8 | $GOPATH/bin/sqlfmt 9 | ``` 10 | 11 | ## Usage 12 | 13 | - You can either: 14 | 15 | + Provide the path to one or more SQL files as command line arguments: 16 | 17 | ```sh 18 | $ sqlfmt testdata/select_where.input.sql 19 | select 20 | foo, 21 | bar 22 | from 23 | baz 24 | where 25 | foo > 5 26 | and bar < 2 27 | ``` 28 | 29 | + Or, directly provide the SQL string via stdin: 30 | 31 | ```sh 32 | $ echo "select * from users" | sqlfmt 33 | select 34 | * 35 | from 36 | users 37 | ``` 38 | 39 | ```sh 40 | $ sqlfmt < testdata/like.input.sql 41 | select 42 | foo, 43 | bar 44 | from 45 | baz 46 | where 47 | foo like 'abd%' 48 | or foo like 'ada%' escape '!' 49 | or foo not like 'abd%' 50 | or foo not like 'ada%' escape '!' 51 | or foo ilike 'efg%' 52 | or foo ilike 'ada%' escape '!' 53 | or foo not ilike 'efg%' 54 | or foo not ilike 'ada%' escape '!' 55 | ``` 56 | 57 | - View [testdata](./testdata) for more examples. 58 | -------------------------------------------------------------------------------- /cmd/sqlfmt/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/jackc/sqlfmt" 12 | ) 13 | 14 | const Version = "0.1.0" 15 | 16 | var options struct { 17 | write bool 18 | upper bool 19 | version bool 20 | } 21 | 22 | type job struct { 23 | name string 24 | r io.ReadCloser 25 | w io.WriteCloser 26 | } 27 | 28 | func (j *job) run() error { 29 | if j.r == nil { 30 | var err error 31 | j.r, err = os.Open(j.name) 32 | if err != nil { 33 | return err 34 | } 35 | } 36 | 37 | input, err := ioutil.ReadAll(j.r) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | err = j.r.Close() 43 | if err != nil { 44 | return err 45 | } 46 | 47 | lexer := sqlfmt.NewSqlLexer(string(input)) 48 | stmt, err := sqlfmt.Parse(lexer) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | var inPlace bool 54 | var tmpPath string 55 | 56 | if j.w == nil { 57 | dir := filepath.Dir(j.name) 58 | base := filepath.Base(j.name) 59 | tmpPath = filepath.Join(dir, "."+base+".sqlfmt") 60 | j.w, err = os.Create(tmpPath) 61 | if err != nil { 62 | return err 63 | } 64 | inPlace = true 65 | } 66 | 67 | r := sqlfmt.NewTextRenderer(j.w) 68 | 69 | r.UpperCase = options.upper 70 | 71 | stmt.RenderTo(r) 72 | if r.Error() != nil { 73 | return err 74 | } 75 | 76 | if inPlace { 77 | err = j.w.Close() 78 | if err != nil { 79 | return err 80 | } 81 | err = os.Rename(tmpPath, j.name) 82 | if err != nil { 83 | return err 84 | } 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func main() { 91 | flag.Usage = func() { 92 | fmt.Fprintf(os.Stderr, "usage: %s [options] [path ...]\n", os.Args[0]) 93 | flag.PrintDefaults() 94 | } 95 | 96 | flag.BoolVar(&options.write, "w", false, "write result to (source) file instead of stdout") 97 | flag.BoolVar(&options.upper, "u", false, "format with upper-case") 98 | flag.BoolVar(&options.version, "version", false, "print version and exit") 99 | flag.Parse() 100 | 101 | if options.version { 102 | fmt.Printf("sqlfmt v%v\n", Version) 103 | os.Exit(0) 104 | } 105 | 106 | var jobs []job 107 | 108 | if len(flag.Args()) > 0 { 109 | for _, fp := range flag.Args() { 110 | j := job{name: fp} 111 | if !options.write { 112 | j.w = os.Stdout 113 | } 114 | jobs = append(jobs, j) 115 | } 116 | } else { 117 | jobs = append(jobs, job{r: os.Stdin, w: os.Stdout}) 118 | } 119 | 120 | var errors []error 121 | 122 | for _, j := range jobs { 123 | if err := j.run(); err != nil { 124 | errors = append(errors, err) 125 | } 126 | } 127 | 128 | if len(errors) > 0 { 129 | for _, e := range errors { 130 | fmt.Fprintln(os.Stderr, e) 131 | } 132 | os.Exit(1) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /cmd/sqlfmt/sqlfmt_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "runtime" 11 | "testing" 12 | ) 13 | 14 | func TestMain(m *testing.M) { 15 | os.MkdirAll("tmp", os.ModeDir|os.ModePerm) 16 | exe := "tmp/sqlfmt" 17 | if runtime.GOOS == "windows" { 18 | exe += ".exe" 19 | } 20 | err := exec.Command("go", "build", "-o", exe).Run() 21 | if err != nil { 22 | fmt.Println("Failed to build sqlfmt binary:", err) 23 | os.Exit(1) 24 | } 25 | 26 | os.Exit(m.Run()) 27 | } 28 | 29 | func sqlfmt(sql []byte, args ...string) ([]byte, error) { 30 | cmd := exec.Command("tmp/sqlfmt", args...) 31 | stdin, err := cmd.StdinPipe() 32 | if err != nil { 33 | return nil, fmt.Errorf("cmd.StdinPipe failed: %v", err) 34 | } 35 | stdout, err := cmd.StdoutPipe() 36 | if err != nil { 37 | return nil, fmt.Errorf("cmd.StdoutPipe failed: %v", err) 38 | } 39 | stderr, err := cmd.StderrPipe() 40 | if err != nil { 41 | return nil, fmt.Errorf("cmd.StderrPipe failed: %v", err) 42 | } 43 | 44 | err = cmd.Start() 45 | if err != nil { 46 | return nil, fmt.Errorf("cmd.Start failed: %v", err) 47 | } 48 | 49 | _, err = stdin.Write(sql) 50 | if err != nil { 51 | return nil, fmt.Errorf("stdin.Write failed: %v", err) 52 | } 53 | 54 | err = stdin.Close() 55 | if err != nil { 56 | return nil, fmt.Errorf("stdin.Close failed: %v", err) 57 | } 58 | 59 | output, err := ioutil.ReadAll(stdout) 60 | if err != nil { 61 | return nil, fmt.Errorf("ioutil.ReadAll(stdout) failed: %v", err) 62 | } 63 | 64 | errout, err := ioutil.ReadAll(stderr) 65 | if err != nil { 66 | return nil, fmt.Errorf("ioutil.ReadAll(stderr) failed: %v", err) 67 | } 68 | 69 | err = cmd.Wait() 70 | if err != nil { 71 | return nil, fmt.Errorf("cmd.Wait failed: %v\n%s", err, string(errout)) 72 | } 73 | 74 | return output, nil 75 | } 76 | 77 | func TestFileInput(t *testing.T) { 78 | inputFile := "simple_select_without_from.input.sql" 79 | expectedOutputFile := "simple_select_without_from.golden.sql" 80 | 81 | expected, err := ioutil.ReadFile(filepath.Join("../../testdata", expectedOutputFile)) 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | 86 | filePath := filepath.Join("../../testdata", inputFile) 87 | output, err := sqlfmt(nil, filePath) 88 | if err != nil { 89 | t.Fatalf("sqlfmt failed with %s: %v", filePath, err) 90 | } 91 | 92 | if bytes.Compare(output, expected) != 0 { 93 | actualFileName := filepath.Join("tmp", "TestFileInput.sql") 94 | err = ioutil.WriteFile(actualFileName, output, os.ModePerm) 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | t.Errorf("Given %s, did not receive %s. Unexpected output written to %s", filePath, expectedOutputFile, actualFileName) 100 | } 101 | } 102 | 103 | func TestFileFormatInPlace(t *testing.T) { 104 | inputFile := "simple_select_without_from.input.sql" 105 | expectedOutputFile := "simple_select_without_from.golden.sql" 106 | expectedOutputPath := filepath.Join("../../testdata", expectedOutputFile) 107 | 108 | expected, err := ioutil.ReadFile(expectedOutputPath) 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | 113 | sourcePath := filepath.Join("../../testdata", inputFile) 114 | source, err := ioutil.ReadFile(sourcePath) 115 | if err != nil { 116 | t.Fatal(err) 117 | } 118 | 119 | tmpFilePath := filepath.Join("tmp", inputFile) 120 | err = ioutil.WriteFile(tmpFilePath, source, os.ModePerm) 121 | if err != nil { 122 | t.Fatal(err) 123 | } 124 | 125 | output, err := sqlfmt(nil, "-w", tmpFilePath) 126 | if err != nil { 127 | t.Fatalf("sqlfmt failed with %s: %v", tmpFilePath, err) 128 | } 129 | 130 | if len(output) != 0 { 131 | t.Fatal("Expected output to be empty, but it wasn't") 132 | } 133 | 134 | output, err = ioutil.ReadFile(tmpFilePath) 135 | if err != nil { 136 | t.Fatal(err) 137 | } 138 | 139 | if bytes.Compare(output, expected) != 0 { 140 | t.Errorf("Given %s, did not receive %s. Unexpected output written to %s", sourcePath, expectedOutputPath, tmpFilePath) 141 | } 142 | } 143 | 144 | func TestMultipleFileFormatInPlace(t *testing.T) { 145 | tests := []struct { 146 | Name string 147 | Source []byte 148 | Expected []byte 149 | TmpFilePath string 150 | }{ 151 | {Name: "between"}, 152 | {Name: "bitconst"}, 153 | } 154 | 155 | var err error 156 | args := []string{"-w"} 157 | for i, _ := range tests { 158 | sourcePath := filepath.Join("../../testdata", tests[i].Name+".input.sql") 159 | tests[i].Source, err = ioutil.ReadFile(sourcePath) 160 | if err != nil { 161 | t.Fatal(err) 162 | } 163 | 164 | expectedOutputPath := filepath.Join("../../testdata", tests[i].Name+".golden.sql") 165 | tests[i].Expected, err = ioutil.ReadFile(expectedOutputPath) 166 | if err != nil { 167 | t.Fatal(err) 168 | } 169 | 170 | tests[i].TmpFilePath = filepath.Join("tmp", tests[i].Name+".sql") 171 | err = ioutil.WriteFile(tests[i].TmpFilePath, tests[i].Source, os.ModePerm) 172 | if err != nil { 173 | t.Fatal(err) 174 | } 175 | 176 | args = append(args, tests[i].TmpFilePath) 177 | } 178 | 179 | output, err := sqlfmt(nil, args...) 180 | if err != nil { 181 | t.Fatalf("sqlfmt failed with %s: %v", args, err) 182 | } 183 | 184 | if len(output) != 0 { 185 | t.Fatal("Expected output to be empty, but it wasn't") 186 | } 187 | 188 | for _, tt := range tests { 189 | output, err = ioutil.ReadFile(tt.TmpFilePath) 190 | if err != nil { 191 | t.Fatal(err) 192 | } 193 | 194 | if bytes.Compare(output, tt.Expected) != 0 { 195 | t.Errorf("%s: unexpected output written to %s", tt.Name, tt.TmpFilePath) 196 | } 197 | } 198 | } 199 | 200 | func TestSqlFmtAll(t *testing.T) { 201 | fileInfos, err := ioutil.ReadDir("../../testdata") 202 | if err != nil { 203 | t.Fatal(err) 204 | } 205 | 206 | for _, fi := range fileInfos { 207 | if fi.Name()[len(fi.Name())-10:] != ".input.sql" { 208 | continue 209 | } 210 | 211 | testName := fi.Name()[:len(fi.Name())-10] 212 | inputPath := filepath.Join("../../testdata", fi.Name()) 213 | goldenPath := filepath.Join("../../testdata", testName+".golden.sql") 214 | 215 | input, err := ioutil.ReadFile(inputPath) 216 | if err != nil { 217 | t.Errorf("%s: %v", testName, err) 218 | continue 219 | } 220 | 221 | expected, err := ioutil.ReadFile(goldenPath) 222 | if err != nil { 223 | t.Errorf("%s: %v", testName, err) 224 | continue 225 | } 226 | 227 | output, err := sqlfmt(input) 228 | if err != nil { 229 | t.Errorf("%s: sqlfmt failed with: %v", testName, err) 230 | continue 231 | } 232 | 233 | if bytes.Compare(output, expected) != 0 { 234 | actualFileName := filepath.Join("tmp", fmt.Sprintf("%s.sql", testName)) 235 | err = ioutil.WriteFile(actualFileName, output, os.ModePerm) 236 | if err != nil { 237 | t.Fatal(err) 238 | } 239 | 240 | t.Errorf("%s: Unexpected output written to %s", testName, actualFileName) 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /integration_test.go: -------------------------------------------------------------------------------- 1 | package sqlfmt_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "testing" 10 | 11 | "github.com/jackc/sqlfmt" 12 | ) 13 | 14 | func TestIntegration(t *testing.T) { 15 | // TODO - put these notes in testdata input files when sqlfmt handles comments 16 | // {inputFile: "b_expr.sql"}, // b_expr is duplicated subset of a_expr -- test its clauses 17 | // {inputFile: "in.sql"}, // TODO - fix formatting when spacing / new line is improved 18 | 19 | fileInfos, err := ioutil.ReadDir("testdata") 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | for _, fi := range fileInfos { 25 | if fi.Name()[len(fi.Name())-10:] != ".input.sql" { 26 | continue 27 | } 28 | 29 | testName := fi.Name()[:len(fi.Name())-10] 30 | inputPath := filepath.Join("testdata", fi.Name()) 31 | goldenPath := filepath.Join("testdata", testName+".golden.sql") 32 | 33 | input, err := ioutil.ReadFile(inputPath) 34 | if err != nil { 35 | t.Errorf("%s: %v", testName, err) 36 | continue 37 | } 38 | 39 | expected, err := ioutil.ReadFile(goldenPath) 40 | if err != nil { 41 | t.Errorf("%s: %v", testName, err) 42 | continue 43 | } 44 | 45 | lexer := sqlfmt.NewSqlLexer(string(input)) 46 | stmt, err := sqlfmt.Parse(lexer) 47 | if err != nil { 48 | t.Errorf("%s: Given %s, %v", testName, inputPath, err) 49 | continue 50 | } 51 | 52 | var outBuf bytes.Buffer 53 | r := sqlfmt.NewTextRenderer(&outBuf) 54 | stmt.RenderTo(r) 55 | 56 | if outBuf.String() != string(expected) { 57 | actualFileName := filepath.Join("tmp", fmt.Sprintf("%s.sql", testName)) 58 | err = ioutil.WriteFile(actualFileName, outBuf.Bytes(), os.ModePerm) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | t.Errorf("%s: Unexpected output written to %s", testName, actualFileName) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /kwlist.go: -------------------------------------------------------------------------------- 1 | package sqlfmt 2 | 3 | // Derived from PostgreSQL -- ./src/include/parser/kwlist.h 4 | 5 | var keywords map[string]int 6 | 7 | func init() { 8 | keywords = make(map[string]int) 9 | /* name, value, category */ 10 | keywords["abort"] = ABORT_P 11 | keywords["absolute"] = ABSOLUTE_P 12 | keywords["access"] = ACCESS 13 | keywords["action"] = ACTION 14 | keywords["add"] = ADD_P 15 | keywords["admin"] = ADMIN 16 | keywords["after"] = AFTER 17 | keywords["aggregate"] = AGGREGATE 18 | keywords["all"] = ALL 19 | keywords["also"] = ALSO 20 | keywords["alter"] = ALTER 21 | keywords["always"] = ALWAYS 22 | keywords["analyse"] = ANALYSE /* British spelling */ 23 | keywords["analyze"] = ANALYZE 24 | keywords["and"] = AND 25 | keywords["any"] = ANY 26 | keywords["array"] = ARRAY 27 | keywords["as"] = AS 28 | keywords["asc"] = ASC 29 | keywords["assertion"] = ASSERTION 30 | keywords["assignment"] = ASSIGNMENT 31 | keywords["asymmetric"] = ASYMMETRIC 32 | keywords["at"] = AT 33 | keywords["attribute"] = ATTRIBUTE 34 | keywords["authorization"] = AUTHORIZATION 35 | keywords["backward"] = BACKWARD 36 | keywords["before"] = BEFORE 37 | keywords["begin"] = BEGIN_P 38 | keywords["between"] = BETWEEN 39 | keywords["bigint"] = BIGINT 40 | keywords["binary"] = BINARY 41 | keywords["bit"] = BIT 42 | keywords["boolean"] = BOOLEAN_P 43 | keywords["both"] = BOTH 44 | keywords["by"] = BY 45 | keywords["cache"] = CACHE 46 | keywords["called"] = CALLED 47 | keywords["cascade"] = CASCADE 48 | keywords["cascaded"] = CASCADED 49 | keywords["case"] = CASE 50 | keywords["cast"] = CAST 51 | keywords["catalog"] = CATALOG_P 52 | keywords["chain"] = CHAIN 53 | keywords["char"] = CHAR_P 54 | keywords["character"] = CHARACTER 55 | keywords["characteristics"] = CHARACTERISTICS 56 | keywords["check"] = CHECK 57 | keywords["checkpoint"] = CHECKPOINT 58 | keywords["class"] = CLASS 59 | keywords["close"] = CLOSE 60 | keywords["cluster"] = CLUSTER 61 | keywords["coalesce"] = COALESCE 62 | keywords["collate"] = COLLATE 63 | keywords["collation"] = COLLATION 64 | keywords["column"] = COLUMN 65 | keywords["comment"] = COMMENT 66 | keywords["comments"] = COMMENTS 67 | keywords["commit"] = COMMIT 68 | keywords["committed"] = COMMITTED 69 | keywords["concurrently"] = CONCURRENTLY 70 | keywords["configuration"] = CONFIGURATION 71 | keywords["conflict"] = CONFLICT 72 | keywords["connection"] = CONNECTION 73 | keywords["constraint"] = CONSTRAINT 74 | keywords["constraints"] = CONSTRAINTS 75 | keywords["content"] = CONTENT_P 76 | keywords["continue"] = CONTINUE_P 77 | keywords["conversion"] = CONVERSION_P 78 | keywords["copy"] = COPY 79 | keywords["cost"] = COST 80 | keywords["create"] = CREATE 81 | keywords["cross"] = CROSS 82 | keywords["csv"] = CSV 83 | keywords["cube"] = CUBE 84 | keywords["current"] = CURRENT_P 85 | keywords["current_catalog"] = CURRENT_CATALOG 86 | keywords["current_date"] = CURRENT_DATE 87 | keywords["current_role"] = CURRENT_ROLE 88 | keywords["current_schema"] = CURRENT_SCHEMA 89 | keywords["current_time"] = CURRENT_TIME 90 | keywords["current_timestamp"] = CURRENT_TIMESTAMP 91 | keywords["current_user"] = CURRENT_USER 92 | keywords["cursor"] = CURSOR 93 | keywords["cycle"] = CYCLE 94 | keywords["data"] = DATA_P 95 | keywords["database"] = DATABASE 96 | keywords["day"] = DAY_P 97 | keywords["deallocate"] = DEALLOCATE 98 | keywords["dec"] = DEC 99 | keywords["decimal"] = DECIMAL_P 100 | keywords["declare"] = DECLARE 101 | keywords["default"] = DEFAULT 102 | keywords["defaults"] = DEFAULTS 103 | keywords["deferrable"] = DEFERRABLE 104 | keywords["deferred"] = DEFERRED 105 | keywords["definer"] = DEFINER 106 | keywords["delete"] = DELETE_P 107 | keywords["delimiter"] = DELIMITER 108 | keywords["delimiters"] = DELIMITERS 109 | keywords["desc"] = DESC 110 | keywords["dictionary"] = DICTIONARY 111 | keywords["disable"] = DISABLE_P 112 | keywords["discard"] = DISCARD 113 | keywords["distinct"] = DISTINCT 114 | keywords["do"] = DO 115 | keywords["document"] = DOCUMENT_P 116 | keywords["domain"] = DOMAIN_P 117 | keywords["double"] = DOUBLE_P 118 | keywords["drop"] = DROP 119 | keywords["each"] = EACH 120 | keywords["else"] = ELSE 121 | keywords["enable"] = ENABLE_P 122 | keywords["encoding"] = ENCODING 123 | keywords["encrypted"] = ENCRYPTED 124 | keywords["end"] = END_P 125 | keywords["enum"] = ENUM_P 126 | keywords["escape"] = ESCAPE 127 | keywords["event"] = EVENT 128 | keywords["except"] = EXCEPT 129 | keywords["exclude"] = EXCLUDE 130 | keywords["excluding"] = EXCLUDING 131 | keywords["exclusive"] = EXCLUSIVE 132 | keywords["execute"] = EXECUTE 133 | keywords["exists"] = EXISTS 134 | keywords["explain"] = EXPLAIN 135 | keywords["extension"] = EXTENSION 136 | keywords["external"] = EXTERNAL 137 | keywords["extract"] = EXTRACT 138 | keywords["false"] = FALSE_P 139 | keywords["family"] = FAMILY 140 | keywords["fetch"] = FETCH 141 | keywords["filter"] = FILTER 142 | keywords["first"] = FIRST_P 143 | keywords["float"] = FLOAT_P 144 | keywords["following"] = FOLLOWING 145 | keywords["for"] = FOR 146 | keywords["force"] = FORCE 147 | keywords["foreign"] = FOREIGN 148 | keywords["forward"] = FORWARD 149 | keywords["freeze"] = FREEZE 150 | keywords["from"] = FROM 151 | keywords["full"] = FULL 152 | keywords["function"] = FUNCTION 153 | keywords["functions"] = FUNCTIONS 154 | keywords["global"] = GLOBAL 155 | keywords["grant"] = GRANT 156 | keywords["granted"] = GRANTED 157 | keywords["greatest"] = GREATEST 158 | keywords["group"] = GROUP_P 159 | keywords["grouping"] = GROUPING 160 | keywords["handler"] = HANDLER 161 | keywords["having"] = HAVING 162 | keywords["header"] = HEADER_P 163 | keywords["hold"] = HOLD 164 | keywords["hour"] = HOUR_P 165 | keywords["identity"] = IDENTITY_P 166 | keywords["if"] = IF_P 167 | keywords["ilike"] = ILIKE 168 | keywords["immediate"] = IMMEDIATE 169 | keywords["immutable"] = IMMUTABLE 170 | keywords["implicit"] = IMPLICIT_P 171 | keywords["import"] = IMPORT_P 172 | keywords["in"] = IN_P 173 | keywords["including"] = INCLUDING 174 | keywords["increment"] = INCREMENT 175 | keywords["index"] = INDEX 176 | keywords["indexes"] = INDEXES 177 | keywords["inherit"] = INHERIT 178 | keywords["inherits"] = INHERITS 179 | keywords["initially"] = INITIALLY 180 | keywords["inline"] = INLINE_P 181 | keywords["inner"] = INNER_P 182 | keywords["inout"] = INOUT 183 | keywords["input"] = INPUT_P 184 | keywords["insensitive"] = INSENSITIVE 185 | keywords["insert"] = INSERT 186 | keywords["instead"] = INSTEAD 187 | keywords["int"] = INT_P 188 | keywords["integer"] = INTEGER 189 | keywords["intersect"] = INTERSECT 190 | keywords["interval"] = INTERVAL 191 | keywords["into"] = INTO 192 | keywords["invoker"] = INVOKER 193 | keywords["is"] = IS 194 | keywords["isnull"] = ISNULL 195 | keywords["isolation"] = ISOLATION 196 | keywords["join"] = JOIN 197 | keywords["key"] = KEY 198 | keywords["label"] = LABEL 199 | keywords["language"] = LANGUAGE 200 | keywords["large"] = LARGE_P 201 | keywords["last"] = LAST_P 202 | keywords["lateral"] = LATERAL_P 203 | keywords["leading"] = LEADING 204 | keywords["leakproof"] = LEAKPROOF 205 | keywords["least"] = LEAST 206 | keywords["left"] = LEFT 207 | keywords["level"] = LEVEL 208 | keywords["like"] = LIKE 209 | keywords["limit"] = LIMIT 210 | keywords["listen"] = LISTEN 211 | keywords["load"] = LOAD 212 | keywords["local"] = LOCAL 213 | keywords["localtime"] = LOCALTIME 214 | keywords["localtimestamp"] = LOCALTIMESTAMP 215 | keywords["location"] = LOCATION 216 | keywords["lock"] = LOCK_P 217 | keywords["locked"] = LOCKED 218 | keywords["logged"] = LOGGED 219 | keywords["mapping"] = MAPPING 220 | keywords["match"] = MATCH 221 | keywords["materialized"] = MATERIALIZED 222 | keywords["maxvalue"] = MAXVALUE 223 | keywords["minute"] = MINUTE_P 224 | keywords["minvalue"] = MINVALUE 225 | keywords["mode"] = MODE 226 | keywords["month"] = MONTH_P 227 | keywords["move"] = MOVE 228 | keywords["name"] = NAME_P 229 | keywords["names"] = NAMES 230 | keywords["national"] = NATIONAL 231 | keywords["natural"] = NATURAL 232 | keywords["nchar"] = NCHAR 233 | keywords["next"] = NEXT 234 | keywords["no"] = NO 235 | keywords["none"] = NONE 236 | keywords["not"] = NOT 237 | keywords["nothing"] = NOTHING 238 | keywords["notify"] = NOTIFY 239 | keywords["notnull"] = NOTNULL 240 | keywords["nowait"] = NOWAIT 241 | keywords["null"] = NULL_P 242 | keywords["nullif"] = NULLIF 243 | keywords["nulls"] = NULLS_P 244 | keywords["numeric"] = NUMERIC 245 | keywords["object"] = OBJECT_P 246 | keywords["of"] = OF 247 | keywords["off"] = OFF 248 | keywords["offset"] = OFFSET 249 | keywords["oids"] = OIDS 250 | keywords["on"] = ON 251 | keywords["only"] = ONLY 252 | keywords["operator"] = OPERATOR 253 | keywords["option"] = OPTION 254 | keywords["options"] = OPTIONS 255 | keywords["or"] = OR 256 | keywords["order"] = ORDER 257 | keywords["ordinality"] = ORDINALITY 258 | keywords["out"] = OUT_P 259 | keywords["outer"] = OUTER_P 260 | keywords["over"] = OVER 261 | keywords["overlaps"] = OVERLAPS 262 | keywords["overlay"] = OVERLAY 263 | keywords["owned"] = OWNED 264 | keywords["owner"] = OWNER 265 | keywords["parser"] = PARSER 266 | keywords["partial"] = PARTIAL 267 | keywords["partition"] = PARTITION 268 | keywords["passing"] = PASSING 269 | keywords["password"] = PASSWORD 270 | keywords["placing"] = PLACING 271 | keywords["plans"] = PLANS 272 | keywords["policy"] = POLICY 273 | keywords["position"] = POSITION 274 | keywords["preceding"] = PRECEDING 275 | keywords["precision"] = PRECISION 276 | keywords["prepare"] = PREPARE 277 | keywords["prepared"] = PREPARED 278 | keywords["preserve"] = PRESERVE 279 | keywords["primary"] = PRIMARY 280 | keywords["prior"] = PRIOR 281 | keywords["privileges"] = PRIVILEGES 282 | keywords["procedural"] = PROCEDURAL 283 | keywords["procedure"] = PROCEDURE 284 | keywords["program"] = PROGRAM 285 | keywords["quote"] = QUOTE 286 | keywords["range"] = RANGE 287 | keywords["read"] = READ 288 | keywords["real"] = REAL 289 | keywords["reassign"] = REASSIGN 290 | keywords["recheck"] = RECHECK 291 | keywords["recursive"] = RECURSIVE 292 | keywords["ref"] = REF 293 | keywords["references"] = REFERENCES 294 | keywords["refresh"] = REFRESH 295 | keywords["reindex"] = REINDEX 296 | keywords["relative"] = RELATIVE_P 297 | keywords["release"] = RELEASE 298 | keywords["rename"] = RENAME 299 | keywords["repeatable"] = REPEATABLE 300 | keywords["replace"] = REPLACE 301 | keywords["replica"] = REPLICA 302 | keywords["reset"] = RESET 303 | keywords["restart"] = RESTART 304 | keywords["restrict"] = RESTRICT 305 | keywords["returning"] = RETURNING 306 | keywords["returns"] = RETURNS 307 | keywords["revoke"] = REVOKE 308 | keywords["right"] = RIGHT 309 | keywords["role"] = ROLE 310 | keywords["rollback"] = ROLLBACK 311 | keywords["rollup"] = ROLLUP 312 | keywords["row"] = ROW 313 | keywords["rows"] = ROWS 314 | keywords["rule"] = RULE 315 | keywords["savepoint"] = SAVEPOINT 316 | keywords["schema"] = SCHEMA 317 | keywords["scroll"] = SCROLL 318 | keywords["search"] = SEARCH 319 | keywords["second"] = SECOND_P 320 | keywords["security"] = SECURITY 321 | keywords["select"] = SELECT 322 | keywords["sequence"] = SEQUENCE 323 | keywords["sequences"] = SEQUENCES 324 | keywords["serializable"] = SERIALIZABLE 325 | keywords["server"] = SERVER 326 | keywords["session"] = SESSION 327 | keywords["session_user"] = SESSION_USER 328 | keywords["set"] = SET 329 | keywords["setof"] = SETOF 330 | keywords["sets"] = SETS 331 | keywords["share"] = SHARE 332 | keywords["show"] = SHOW 333 | keywords["similar"] = SIMILAR 334 | keywords["simple"] = SIMPLE 335 | keywords["skip"] = SKIP 336 | keywords["smallint"] = SMALLINT 337 | keywords["snapshot"] = SNAPSHOT 338 | keywords["some"] = SOME 339 | keywords["sql"] = SQL_P 340 | keywords["stable"] = STABLE 341 | keywords["standalone"] = STANDALONE_P 342 | keywords["start"] = START 343 | keywords["statement"] = STATEMENT 344 | keywords["statistics"] = STATISTICS 345 | keywords["stdin"] = STDIN 346 | keywords["stdout"] = STDOUT 347 | keywords["storage"] = STORAGE 348 | keywords["strict"] = STRICT_P 349 | keywords["strip"] = STRIP_P 350 | keywords["substring"] = SUBSTRING 351 | keywords["symmetric"] = SYMMETRIC 352 | keywords["sysid"] = SYSID 353 | keywords["system"] = SYSTEM_P 354 | keywords["table"] = TABLE 355 | keywords["tables"] = TABLES 356 | keywords["tablesample"] = TABLESAMPLE 357 | keywords["tablespace"] = TABLESPACE 358 | keywords["temp"] = TEMP 359 | keywords["template"] = TEMPLATE 360 | keywords["temporary"] = TEMPORARY 361 | keywords["text"] = TEXT_P 362 | keywords["then"] = THEN 363 | keywords["time"] = TIME 364 | keywords["timestamp"] = TIMESTAMP 365 | keywords["to"] = TO 366 | keywords["trailing"] = TRAILING 367 | keywords["transaction"] = TRANSACTION 368 | keywords["transform"] = TRANSFORM 369 | keywords["treat"] = TREAT 370 | keywords["trigger"] = TRIGGER 371 | keywords["trim"] = TRIM 372 | keywords["true"] = TRUE_P 373 | keywords["truncate"] = TRUNCATE 374 | keywords["trusted"] = TRUSTED 375 | keywords["type"] = TYPE_P 376 | keywords["types"] = TYPES_P 377 | keywords["unbounded"] = UNBOUNDED 378 | keywords["uncommitted"] = UNCOMMITTED 379 | keywords["unencrypted"] = UNENCRYPTED 380 | keywords["union"] = UNION 381 | keywords["unique"] = UNIQUE 382 | keywords["unknown"] = UNKNOWN 383 | keywords["unlisten"] = UNLISTEN 384 | keywords["unlogged"] = UNLOGGED 385 | keywords["until"] = UNTIL 386 | keywords["update"] = UPDATE 387 | keywords["user"] = USER 388 | keywords["using"] = USING 389 | keywords["vacuum"] = VACUUM 390 | keywords["valid"] = VALID 391 | keywords["validate"] = VALIDATE 392 | keywords["validator"] = VALIDATOR 393 | keywords["value"] = VALUE_P 394 | keywords["values"] = VALUES 395 | keywords["varchar"] = VARCHAR 396 | keywords["variadic"] = VARIADIC 397 | keywords["varying"] = VARYING 398 | keywords["verbose"] = VERBOSE 399 | keywords["version"] = VERSION_P 400 | keywords["view"] = VIEW 401 | keywords["views"] = VIEWS 402 | keywords["volatile"] = VOLATILE 403 | keywords["when"] = WHEN 404 | keywords["where"] = WHERE 405 | keywords["whitespace"] = WHITESPACE_P 406 | keywords["window"] = WINDOW 407 | keywords["with"] = WITH 408 | keywords["within"] = WITHIN 409 | keywords["without"] = WITHOUT 410 | keywords["work"] = WORK 411 | keywords["wrapper"] = WRAPPER 412 | keywords["write"] = WRITE 413 | keywords["xml"] = XML_P 414 | keywords["xmlattributes"] = XMLATTRIBUTES 415 | keywords["xmlconcat"] = XMLCONCAT 416 | keywords["xmlelement"] = XMLELEMENT 417 | keywords["xmlexists"] = XMLEXISTS 418 | keywords["xmlforest"] = XMLFOREST 419 | keywords["xmlparse"] = XMLPARSE 420 | keywords["xmlpi"] = XMLPI 421 | keywords["xmlroot"] = XMLROOT 422 | keywords["xmlserialize"] = XMLSERIALIZE 423 | keywords["year"] = YEAR_P 424 | keywords["yes"] = YES_P 425 | keywords["zone"] = ZONE 426 | } 427 | -------------------------------------------------------------------------------- /lex.go: -------------------------------------------------------------------------------- 1 | //go:generate -command yacc go tool yacc 2 | //go:generate yacc -o sql.go sql.y 3 | package sqlfmt 4 | 5 | import ( 6 | "log" 7 | "strings" 8 | "unicode" 9 | "unicode/utf8" 10 | ) 11 | 12 | type stateFn func(*sqlLex) stateFn 13 | 14 | type token struct { 15 | typ int 16 | src string 17 | } 18 | 19 | type sqlLex struct { 20 | src string 21 | start int 22 | pos int 23 | width int 24 | state stateFn 25 | tokens []token 26 | stmt *SelectStmt 27 | } 28 | 29 | func (x *sqlLex) Lex(yylval *yySymType) int { 30 | token := x.tokens[0] 31 | x.tokens = x.tokens[1:] 32 | 33 | yylval.str = token.src 34 | return token.typ 35 | } 36 | 37 | // The parser calls this method on a parse error. 38 | func (x *sqlLex) Error(s string) { 39 | log.Printf("parse error: %s at character %d", s, x.start) 40 | } 41 | 42 | func NewSqlLexer(src string) *sqlLex { 43 | x := &sqlLex{src: src, 44 | tokens: make([]token, 0), 45 | state: blankState, 46 | } 47 | 48 | for x.state != nil { 49 | x.state = x.state(x) 50 | } 51 | 52 | x.append(token{typ: eof}) 53 | 54 | return x 55 | } 56 | 57 | func (l *sqlLex) append(t token) { 58 | l.tokens = append(l.tokens, t) 59 | 60 | if len(l.tokens) == 1 { 61 | return 62 | } 63 | 64 | prevToken := &l.tokens[len(l.tokens)-2] 65 | 66 | switch t.typ { 67 | case BETWEEN, IN_P, LIKE, ILIKE, SIMILAR: 68 | if prevToken.typ == NOT { 69 | prevToken.typ = NOT_LA 70 | } 71 | case FIRST_P, LAST_P: 72 | if prevToken.typ == NULLS_P { 73 | prevToken.typ = NULLS_LA 74 | } 75 | case TIME, ORDINALITY: 76 | if prevToken.typ == WITH { 77 | prevToken.typ = WITH_LA 78 | } 79 | } 80 | } 81 | 82 | func (l *sqlLex) next() (r rune) { 83 | if l.pos >= len(l.src) { 84 | l.width = 0 // because backing up from having read eof should read eof again 85 | return 0 86 | } 87 | 88 | r, l.width = utf8.DecodeRuneInString(l.src[l.pos:]) 89 | l.pos += l.width 90 | 91 | return r 92 | } 93 | 94 | func (l *sqlLex) unnext() { 95 | l.pos -= l.width 96 | } 97 | 98 | func (l *sqlLex) ignore() { 99 | l.start = l.pos 100 | } 101 | 102 | func (l *sqlLex) acceptRunFunc(f func(rune) bool) { 103 | for f(l.next()) { 104 | } 105 | l.unnext() 106 | } 107 | 108 | func blankState(l *sqlLex) stateFn { 109 | switch r := l.next(); { 110 | case r == 0: 111 | return nil 112 | case r == ',' || r == '(' || r == ')' || r == '[' || r == ']' || r == ';': 113 | return lexSimple 114 | case r == '\'': 115 | return lexStringConst 116 | case r == '"': 117 | return lexQuotedIdentifier 118 | case r == ':' || r == '.': 119 | return lexAlmostOperator 120 | case isOperator(r): 121 | return lexOperator 122 | case r == 'b' || r == 'B' || r == 'x' || r == 'X': 123 | return lexPossibleBitString 124 | case unicode.IsDigit(r): 125 | return lexNumber 126 | case isWhitespace(r): 127 | l.skipWhitespace() 128 | return blankState 129 | case isAlphanumeric(r): 130 | return lexAlphanumeric 131 | } 132 | return nil 133 | } 134 | 135 | func lexNumber(l *sqlLex) stateFn { 136 | typ := ICONST 137 | l.acceptRunFunc(unicode.IsDigit) 138 | r := l.next() 139 | if r == '.' { 140 | l.acceptRunFunc(unicode.IsDigit) 141 | typ = FCONST 142 | } else { 143 | l.unnext() 144 | } 145 | t := token{src: l.src[l.start:l.pos], typ: typ} 146 | l.append(t) 147 | l.start = l.pos 148 | return blankState 149 | } 150 | 151 | func lexAlphanumeric(l *sqlLex) stateFn { 152 | l.acceptRunFunc(isAlphanumeric) 153 | 154 | t := token{src: l.src[l.start:l.pos]} 155 | 156 | if typ, ok := keywords[strings.ToLower(t.src)]; ok { 157 | t.typ = typ 158 | } else { 159 | t.typ = IDENT 160 | } 161 | 162 | l.append(t) 163 | l.start = l.pos 164 | return blankState 165 | } 166 | 167 | func lexStringConst(l *sqlLex) stateFn { 168 | for { 169 | var r rune 170 | r = l.next() 171 | if r == 0 { 172 | return nil // error for EOF inside of string literal 173 | } 174 | 175 | if r == '\'' { 176 | r = l.next() 177 | if r != '\'' { 178 | l.unnext() 179 | t := token{src: l.src[l.start:l.pos]} 180 | t.typ = SCONST 181 | l.append(t) 182 | l.start = l.pos 183 | return blankState 184 | } 185 | } 186 | } 187 | } 188 | 189 | func lexQuotedIdentifier(l *sqlLex) stateFn { 190 | for { 191 | var r rune 192 | r = l.next() 193 | if r == 0 { 194 | return nil // error for EOF inside of string literal 195 | } 196 | 197 | if r == '"' { 198 | r = l.next() 199 | if r != '"' { 200 | l.unnext() 201 | t := token{src: l.src[l.start:l.pos]} 202 | t.typ = IDENT 203 | l.append(t) 204 | l.start = l.pos 205 | return blankState 206 | } 207 | } 208 | } 209 | } 210 | 211 | // lexAlmostOperator is for operator-like ':' and '.' which aren't operators 212 | func lexAlmostOperator(l *sqlLex) stateFn { 213 | l.next() 214 | 215 | t := token{src: l.src[l.start:l.pos]} 216 | switch { 217 | case t.src == "::": 218 | t.typ = TYPECAST 219 | case t.src == "..": 220 | t.typ = DOT_DOT 221 | case t.src == ":=": 222 | t.typ = COLON_EQUALS 223 | default: 224 | l.unnext() 225 | t.typ = int(t.src[0]) 226 | } 227 | t.src = l.src[l.start:l.pos] 228 | 229 | l.append(t) 230 | l.start = l.pos 231 | return blankState 232 | } 233 | 234 | func lexOperator(l *sqlLex) stateFn { 235 | l.acceptRunFunc(isOperator) 236 | 237 | if (l.pos-l.start) >= 2 && l.src[l.start:l.start+2] == "--" { 238 | return lexDashDashComment 239 | } 240 | 241 | t := token{src: l.src[l.start:l.pos]} 242 | switch { 243 | case t.src == "+" || t.src == "-" || t.src == "*" || t.src == "/" || t.src == "%" || t.src == "^" || t.src == "<" || t.src == ">" || t.src == "=" || t.src == "[" || t.src == "]" || t.src == ":": 244 | t.typ = int(t.src[0]) 245 | case t.src == "=>": 246 | t.typ = EQUALS_GREATER 247 | case t.src == "<=": 248 | t.typ = LESS_EQUALS 249 | case t.src == ">=": 250 | t.typ = GREATER_EQUALS 251 | case t.src == "<>", t.src == "!=": 252 | t.typ = NOT_EQUALS 253 | default: 254 | t.typ = Op 255 | } 256 | 257 | l.append(t) 258 | l.start = l.pos 259 | return blankState 260 | } 261 | 262 | func lexSimple(l *sqlLex) stateFn { 263 | l.append(token{int(l.src[l.start]), l.src[l.start:l.pos]}) 264 | l.start = l.pos 265 | return blankState 266 | } 267 | 268 | func lexPossibleBitString(l *sqlLex) stateFn { 269 | var rangeTable unicode.RangeTable 270 | 271 | if l.src[l.start] == 'b' || l.src[l.start] == 'B' { 272 | rangeTable = unicode.RangeTable{ 273 | R16: []unicode.Range16{ 274 | unicode.Range16{Lo: '0', Hi: '1', Stride: 1}, 275 | }, 276 | LatinOffset: 1, 277 | } 278 | } else { 279 | rangeTable = unicode.RangeTable{ 280 | R16: []unicode.Range16{ 281 | unicode.Range16{Lo: '0', Hi: '9', Stride: 1}, 282 | unicode.Range16{Lo: 'A', Hi: 'F', Stride: 1}, 283 | unicode.Range16{Lo: 'a', Hi: 'f', Stride: 1}, 284 | }, 285 | LatinOffset: 3, 286 | } 287 | } 288 | 289 | r := l.next() 290 | if r == '\'' { 291 | l.acceptRunFunc(func(r rune) bool { 292 | return unicode.In(r, &rangeTable) 293 | }) 294 | r = l.next() 295 | if r == '\'' { 296 | t := token{src: l.src[l.start:l.pos], typ: BCONST} 297 | l.append(t) 298 | l.start = l.pos 299 | return blankState 300 | } 301 | return nil // lex error 302 | } else { 303 | l.unnext() 304 | } 305 | 306 | return lexAlphanumeric 307 | } 308 | 309 | func lexDashDashComment(l *sqlLex) stateFn { 310 | var r rune 311 | for r = l.next(); r != '\n'; r = l.next() { 312 | } 313 | 314 | // TODO - don't ignore comments 315 | l.ignore() 316 | 317 | return blankState 318 | } 319 | 320 | func (l *sqlLex) skipWhitespace() { 321 | var r rune 322 | for r = l.next(); isWhitespace(r); r = l.next() { 323 | } 324 | 325 | if r != 0 { 326 | l.unnext() 327 | } 328 | 329 | l.ignore() 330 | } 331 | 332 | func isWhitespace(r rune) bool { 333 | return unicode.IsSpace(r) 334 | } 335 | 336 | func isAlphanumeric(r rune) bool { 337 | return r == '_' || unicode.In(r, unicode.Letter, unicode.Digit) 338 | } 339 | 340 | func isOperator(r rune) bool { 341 | // list of operator characters from 342 | // http://www.postgresql.org/docs/9.4/static/sql-createoperator.html 343 | return strings.IndexRune("+-*/<>=~!@#%^&|`?", r) != -1 344 | } 345 | -------------------------------------------------------------------------------- /parsed_types.go: -------------------------------------------------------------------------------- 1 | package sqlfmt 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | func Parse(lexer *sqlLex) (stmt *SelectStmt, err error) { 8 | if rc := yyParse(lexer); rc != 0 { 9 | return nil, errors.New("Parse failed") 10 | } 11 | 12 | return lexer.stmt, nil 13 | } 14 | 15 | type Expr interface { 16 | RenderTo(Renderer) 17 | } 18 | 19 | type PgType struct { 20 | Name AnyName 21 | OptInterval *OptInterval 22 | Setof bool 23 | ArrayWord bool 24 | ArrayBounds []IntegerConst 25 | TypeMods []Expr 26 | CharSet string 27 | WithTimeZone bool 28 | } 29 | 30 | func (t PgType) RenderTo(r Renderer) { 31 | if t.Setof { 32 | r.Text("setof", KeywordToken) 33 | } 34 | 35 | t.Name.RenderTo(r) 36 | 37 | if t.OptInterval != nil { 38 | t.OptInterval.RenderTo(r) 39 | } 40 | 41 | if t.ArrayWord { 42 | r.Text("array", KeywordToken) 43 | } 44 | 45 | for _, ab := range t.ArrayBounds { 46 | r.Text("[", SymbolToken) 47 | r.Text(string(ab), ConstantToken) 48 | r.Text("]", SymbolToken) 49 | } 50 | 51 | if len(t.TypeMods) > 0 { 52 | r.Text("(", SymbolToken) 53 | for i, e := range t.TypeMods { 54 | e.RenderTo(r) 55 | if i < len(t.TypeMods)-1 { 56 | r.Text(",", SymbolToken) 57 | } 58 | } 59 | r.Text(")", SymbolToken) 60 | } 61 | 62 | if t.WithTimeZone { 63 | r.Text("with time zone", KeywordToken) 64 | } 65 | 66 | if t.CharSet != "" { 67 | r.Text("character set", KeywordToken) 68 | r.Text(t.CharSet, IdentifierToken) 69 | } 70 | } 71 | 72 | type AnyName []string 73 | 74 | func (an AnyName) RenderTo(r Renderer) { 75 | for i, n := range an { 76 | r.Text(n, IdentifierToken) 77 | if i < len(an)-1 { 78 | r.Text(".", SymbolToken) 79 | } 80 | } 81 | } 82 | 83 | type ColumnRef struct { 84 | Name string 85 | Indirection Indirection 86 | } 87 | 88 | func (cr ColumnRef) RenderTo(r Renderer) { 89 | r.Text(cr.Name, IdentifierToken) 90 | if cr.Indirection != nil { 91 | cr.Indirection.RenderTo(r) 92 | } 93 | } 94 | 95 | type Indirection []IndirectionEl 96 | 97 | func (i Indirection) RenderTo(r Renderer) { 98 | for _, e := range i { 99 | e.RenderTo(r) 100 | } 101 | } 102 | 103 | type IndirectionEl struct { 104 | Name string 105 | LowerSubscript Expr 106 | UpperSubscript Expr 107 | } 108 | 109 | func (ie IndirectionEl) RenderTo(r Renderer) { 110 | if ie.LowerSubscript != nil { 111 | r.Text("[", SymbolToken) 112 | ie.LowerSubscript.RenderTo(r) 113 | if ie.UpperSubscript != nil { 114 | r.Text(":", SymbolToken) 115 | ie.UpperSubscript.RenderTo(r) 116 | } 117 | r.Text("]", SymbolToken) 118 | } else { 119 | r.Text(".", SymbolToken) 120 | r.Text(ie.Name, IdentifierToken) 121 | } 122 | } 123 | 124 | type StringConst string 125 | 126 | func (s StringConst) RenderTo(r Renderer) { 127 | r.Text(string(s), ConstantToken) 128 | } 129 | 130 | type IntegerConst string 131 | 132 | func (s IntegerConst) RenderTo(r Renderer) { 133 | r.Text(string(s), ConstantToken) 134 | } 135 | 136 | type FloatConst string 137 | 138 | func (s FloatConst) RenderTo(r Renderer) { 139 | r.Text(string(s), ConstantToken) 140 | } 141 | 142 | type BoolConst bool 143 | 144 | func (b BoolConst) RenderTo(r Renderer) { 145 | if b { 146 | r.Text("true", KeywordToken) 147 | } else { 148 | r.Text("false", KeywordToken) 149 | } 150 | } 151 | 152 | type NullConst struct{} 153 | 154 | func (n NullConst) RenderTo(r Renderer) { 155 | r.Text("null", KeywordToken) 156 | } 157 | 158 | type BitConst string 159 | 160 | func (b BitConst) RenderTo(r Renderer) { 161 | r.Text(string(b), ConstantToken) 162 | } 163 | 164 | type BooleanExpr struct { 165 | Left Expr 166 | Operator string 167 | Right Expr 168 | } 169 | 170 | func (e BooleanExpr) RenderTo(r Renderer) { 171 | e.Left.RenderTo(r) 172 | r.Control(NewLineToken) 173 | r.Text(e.Operator, SymbolToken) 174 | e.Right.RenderTo(r) 175 | } 176 | 177 | type BinaryExpr struct { 178 | Left Expr 179 | Operator AnyName 180 | Right Expr 181 | } 182 | 183 | func (e BinaryExpr) RenderTo(r Renderer) { 184 | e.Left.RenderTo(r) 185 | r.Control(SpaceToken) 186 | e.Operator.RenderTo(r) 187 | r.Control(SpaceToken) 188 | e.Right.RenderTo(r) 189 | } 190 | 191 | type ArrayConstructorExpr ArrayExpr 192 | 193 | func (ace ArrayConstructorExpr) RenderTo(r Renderer) { 194 | r.Text("array", KeywordToken) 195 | ArrayExpr(ace).RenderTo(r) 196 | } 197 | 198 | type ArrayExpr []Expr 199 | 200 | func (a ArrayExpr) RenderTo(r Renderer) { 201 | r.Text("[", SymbolToken) 202 | for i, e := range a { 203 | e.RenderTo(r) 204 | if i < len(a)-1 { 205 | r.Text(",", SymbolToken) 206 | } 207 | } 208 | r.Text("]", SymbolToken) 209 | } 210 | 211 | type TextOpWithEscapeExpr struct { 212 | Left Expr 213 | Operator string 214 | Right Expr 215 | Escape Expr 216 | } 217 | 218 | func (e TextOpWithEscapeExpr) RenderTo(r Renderer) { 219 | e.Left.RenderTo(r) 220 | r.Text(e.Operator, SymbolToken) 221 | e.Right.RenderTo(r) 222 | 223 | if e.Escape != nil { 224 | r.Text("escape", KeywordToken) 225 | e.Escape.RenderTo(r) 226 | } 227 | } 228 | 229 | type UnaryExpr struct { 230 | Operator AnyName 231 | Expr Expr 232 | } 233 | 234 | func (e UnaryExpr) RenderTo(r Renderer) { 235 | e.Operator.RenderTo(r) 236 | r.Control(RefuseSpaceToken) 237 | e.Expr.RenderTo(r) 238 | } 239 | 240 | type PostfixExpr struct { 241 | Expr Expr 242 | Operator AnyName 243 | } 244 | 245 | func (e PostfixExpr) RenderTo(r Renderer) { 246 | e.Expr.RenderTo(r) 247 | e.Operator.RenderTo(r) 248 | } 249 | 250 | type SubqueryOpExpr struct { 251 | Value Expr 252 | Op SubqueryOp 253 | Type string 254 | Query Expr 255 | } 256 | 257 | func (s SubqueryOpExpr) RenderTo(r Renderer) { 258 | s.Value.RenderTo(r) 259 | s.Op.RenderTo(r) 260 | r.Text(s.Type, KeywordToken) 261 | r.Control(SpaceToken) 262 | s.Query.RenderTo(r) 263 | } 264 | 265 | type SubqueryOp struct { 266 | Operator bool 267 | Name AnyName 268 | } 269 | 270 | func (s SubqueryOp) RenderTo(r Renderer) { 271 | if s.Operator { 272 | r.Text("operator", KeywordToken) 273 | r.Text("(", SymbolToken) 274 | } 275 | s.Name.RenderTo(r) 276 | if s.Operator { 277 | r.Text(")", SymbolToken) 278 | } 279 | } 280 | 281 | type WhenClause struct { 282 | When Expr 283 | Then Expr 284 | } 285 | 286 | func (w WhenClause) RenderTo(r Renderer) { 287 | r.Text("when", KeywordToken) 288 | w.When.RenderTo(r) 289 | r.Text("then", KeywordToken) 290 | r.Control(NewLineToken) 291 | r.Control(IndentToken) 292 | w.Then.RenderTo(r) 293 | r.Control(NewLineToken) 294 | r.Control(UnindentToken) 295 | } 296 | 297 | type InExpr struct { 298 | Value Expr 299 | Not bool 300 | In Expr 301 | } 302 | 303 | func (i InExpr) RenderTo(r Renderer) { 304 | i.Value.RenderTo(r) 305 | 306 | if i.Not { 307 | r.Text("not", KeywordToken) 308 | } 309 | 310 | r.Text("in", KeywordToken) 311 | r.Control(SpaceToken) 312 | 313 | i.In.RenderTo(r) 314 | } 315 | 316 | type BetweenExpr struct { 317 | Expr Expr 318 | Not bool 319 | Symmetric bool 320 | Left Expr 321 | Right Expr 322 | } 323 | 324 | func (b BetweenExpr) RenderTo(r Renderer) { 325 | b.Expr.RenderTo(r) 326 | 327 | if b.Not { 328 | r.Text("not", KeywordToken) 329 | } 330 | 331 | r.Text("between", KeywordToken) 332 | 333 | if b.Symmetric { 334 | r.Text("symmetric", KeywordToken) 335 | } 336 | 337 | b.Left.RenderTo(r) 338 | r.Text("and", KeywordToken) 339 | b.Right.RenderTo(r) 340 | } 341 | 342 | type CaseExpr struct { 343 | CaseArg Expr 344 | WhenClauses []WhenClause 345 | Default Expr 346 | } 347 | 348 | func (c CaseExpr) RenderTo(r Renderer) { 349 | r.Text("case", KeywordToken) 350 | 351 | if c.CaseArg != nil { 352 | c.CaseArg.RenderTo(r) 353 | } 354 | 355 | r.Control(NewLineToken) 356 | 357 | for _, w := range c.WhenClauses { 358 | w.RenderTo(r) 359 | } 360 | 361 | if c.Default != nil { 362 | r.Text("else", KeywordToken) 363 | r.Control(NewLineToken) 364 | r.Control(IndentToken) 365 | c.Default.RenderTo(r) 366 | r.Control(NewLineToken) 367 | r.Control(UnindentToken) 368 | } 369 | 370 | r.Text("end", KeywordToken) 371 | r.Control(NewLineToken) 372 | } 373 | 374 | type ParenExpr struct { 375 | Expr Expr 376 | Indirection Indirection 377 | } 378 | 379 | func (e ParenExpr) RenderTo(r Renderer) { 380 | r.Text("(", SymbolToken) 381 | e.Expr.RenderTo(r) 382 | r.Text(")", SymbolToken) 383 | if e.Indirection != nil { 384 | e.Indirection.RenderTo(r) 385 | } 386 | } 387 | 388 | type TypecastExpr struct { 389 | Expr Expr 390 | Typename PgType 391 | } 392 | 393 | func (t TypecastExpr) RenderTo(r Renderer) { 394 | t.Expr.RenderTo(r) 395 | r.Text("::", SymbolToken) 396 | t.Typename.RenderTo(r) 397 | } 398 | 399 | type ConstTypeExpr struct { 400 | Typename PgType 401 | Expr Expr 402 | } 403 | 404 | func (t ConstTypeExpr) RenderTo(r Renderer) { 405 | t.Typename.RenderTo(r) 406 | t.Expr.RenderTo(r) 407 | } 408 | 409 | type ConstIntervalExpr struct { 410 | Precision IntegerConst 411 | Value Expr 412 | OptInterval *OptInterval 413 | } 414 | 415 | func (i ConstIntervalExpr) RenderTo(r Renderer) { 416 | r.Text("interval", KeywordToken) 417 | if i.Precision != "" { 418 | r.Text("(", SymbolToken) 419 | i.Precision.RenderTo(r) 420 | r.Text(")", SymbolToken) 421 | } 422 | 423 | i.Value.RenderTo(r) 424 | 425 | if i.OptInterval != nil { 426 | i.OptInterval.RenderTo(r) 427 | } 428 | } 429 | 430 | type OptInterval struct { 431 | Left string 432 | Right string 433 | Second *IntervalSecond 434 | } 435 | 436 | func (oi OptInterval) RenderTo(r Renderer) { 437 | if oi.Left != "" { 438 | r.Text(oi.Left, KeywordToken) 439 | } 440 | 441 | if oi.Right != "" { 442 | r.Text("to", KeywordToken) 443 | r.Text(oi.Right, KeywordToken) 444 | } 445 | 446 | if oi.Second != nil { 447 | oi.Second.RenderTo(r) 448 | } 449 | } 450 | 451 | type IntervalSecond struct { 452 | Precision IntegerConst 453 | } 454 | 455 | func (is IntervalSecond) RenderTo(r Renderer) { 456 | r.Text("second", KeywordToken) 457 | if is.Precision != "" { 458 | r.Text("(", SymbolToken) 459 | is.Precision.RenderTo(r) 460 | r.Text(")", SymbolToken) 461 | } 462 | } 463 | 464 | type ExtractExpr ExtractList 465 | 466 | func (ee ExtractExpr) RenderTo(r Renderer) { 467 | r.Text("extract", KeywordToken) 468 | r.Text("(", SymbolToken) 469 | ExtractList(ee).RenderTo(r) 470 | r.Text(")", SymbolToken) 471 | } 472 | 473 | type ExtractList struct { 474 | Extract Expr 475 | Time Expr 476 | } 477 | 478 | func (el ExtractList) RenderTo(r Renderer) { 479 | el.Extract.RenderTo(r) 480 | r.Text("from", KeywordToken) 481 | el.Time.RenderTo(r) 482 | } 483 | 484 | type OverlayExpr OverlayList 485 | 486 | func (oe OverlayExpr) RenderTo(r Renderer) { 487 | r.Text("overlay", KeywordToken) 488 | r.Text("(", SymbolToken) 489 | OverlayList(oe).RenderTo(r) 490 | r.Text(")", SymbolToken) 491 | } 492 | 493 | type OverlayList struct { 494 | Dest Expr 495 | Placing Expr 496 | From Expr 497 | For Expr 498 | } 499 | 500 | func (ol OverlayList) RenderTo(r Renderer) { 501 | ol.Dest.RenderTo(r) 502 | r.Text("placing", KeywordToken) 503 | ol.Placing.RenderTo(r) 504 | r.Text("from", KeywordToken) 505 | ol.From.RenderTo(r) 506 | 507 | if ol.For != nil { 508 | r.Text("for", KeywordToken) 509 | ol.For.RenderTo(r) 510 | } 511 | } 512 | 513 | type PositionExpr PositionList 514 | 515 | func (pe PositionExpr) RenderTo(r Renderer) { 516 | r.Text("position", KeywordToken) 517 | r.Text("(", SymbolToken) 518 | PositionList(pe).RenderTo(r) 519 | r.Text(")", SymbolToken) 520 | } 521 | 522 | type PositionList struct { 523 | Substring Expr 524 | String Expr 525 | } 526 | 527 | func (pl PositionList) RenderTo(r Renderer) { 528 | pl.Substring.RenderTo(r) 529 | r.Text("in", KeywordToken) 530 | pl.String.RenderTo(r) 531 | } 532 | 533 | type SubstrExpr SubstrList 534 | 535 | func (se SubstrExpr) RenderTo(r Renderer) { 536 | r.Text("substring", KeywordToken) 537 | r.Text("(", SymbolToken) 538 | SubstrList(se).RenderTo(r) 539 | r.Text(")", SymbolToken) 540 | } 541 | 542 | type SubstrList struct { 543 | Source Expr 544 | From Expr 545 | For Expr 546 | } 547 | 548 | func (sl SubstrList) RenderTo(r Renderer) { 549 | sl.Source.RenderTo(r) 550 | r.Text("from", KeywordToken) 551 | sl.From.RenderTo(r) 552 | 553 | if sl.For != nil { 554 | r.Text("for", KeywordToken) 555 | sl.For.RenderTo(r) 556 | } 557 | } 558 | 559 | type TrimExpr struct { 560 | Direction string 561 | TrimList 562 | } 563 | 564 | func (te TrimExpr) RenderTo(r Renderer) { 565 | r.Text("trim", KeywordToken) 566 | r.Text("(", SymbolToken) 567 | if te.Direction != "" { 568 | r.Text(te.Direction, KeywordToken) 569 | } 570 | te.TrimList.RenderTo(r) 571 | r.Text(")", SymbolToken) 572 | } 573 | 574 | type TrimList struct { 575 | Left Expr 576 | From bool 577 | Right []Expr 578 | } 579 | 580 | func (tl TrimList) RenderTo(r Renderer) { 581 | if tl.Left != nil { 582 | tl.Left.RenderTo(r) 583 | } 584 | 585 | if tl.From { 586 | r.Text("from", KeywordToken) 587 | } 588 | 589 | for i, e := range tl.Right { 590 | e.RenderTo(r) 591 | if i+1 < len(tl.Right) { 592 | r.Text(",", SymbolToken) 593 | } 594 | } 595 | } 596 | 597 | type XmlElement struct { 598 | Name string 599 | Attributes XmlAttributes 600 | Body []Expr 601 | } 602 | 603 | func (el XmlElement) RenderTo(r Renderer) { 604 | r.Text("xmlelement", KeywordToken) 605 | r.Text("(", SymbolToken) 606 | r.Text("name", KeywordToken) 607 | r.Text(el.Name, IdentifierToken) 608 | 609 | if el.Attributes != nil { 610 | r.Text(",", SymbolToken) 611 | el.Attributes.RenderTo(r) 612 | } 613 | 614 | if el.Body != nil { 615 | for _, e := range el.Body { 616 | r.Text(",", SymbolToken) 617 | e.RenderTo(r) 618 | } 619 | } 620 | 621 | r.Text(")", SymbolToken) 622 | } 623 | 624 | type XmlAttributes []XmlAttributeEl 625 | 626 | func (attrs XmlAttributes) RenderTo(r Renderer) { 627 | r.Text("xmlattributes", KeywordToken) 628 | r.Text("(", SymbolToken) 629 | xmlAttributes(attrs).RenderTo(r) 630 | r.Text(")", SymbolToken) 631 | } 632 | 633 | type XmlAttributeEl struct { 634 | Value Expr 635 | Name string 636 | } 637 | 638 | func (el XmlAttributeEl) RenderTo(r Renderer) { 639 | el.Value.RenderTo(r) 640 | if el.Name != "" { 641 | r.Text("as", KeywordToken) 642 | r.Text(el.Name, IdentifierToken) 643 | } 644 | } 645 | 646 | type XmlExists struct { 647 | Path Expr 648 | Body XmlExistsArgument 649 | } 650 | 651 | func (e XmlExists) RenderTo(r Renderer) { 652 | r.Text("xmlexists", KeywordToken) 653 | r.Text("(", SymbolToken) 654 | e.Path.RenderTo(r) 655 | e.Body.RenderTo(r) 656 | r.Text(")", SymbolToken) 657 | } 658 | 659 | type XmlExistsArgument struct { 660 | LeftByRef bool 661 | Arg Expr 662 | RightByRef bool 663 | } 664 | 665 | func (a XmlExistsArgument) RenderTo(r Renderer) { 666 | r.Text("passing", KeywordToken) 667 | 668 | if a.LeftByRef { 669 | r.Text("by ref", KeywordToken) 670 | } 671 | 672 | a.Arg.RenderTo(r) 673 | 674 | if a.RightByRef { 675 | r.Text("by ref", KeywordToken) 676 | } 677 | } 678 | 679 | type XmlForest []XmlAttributeEl 680 | 681 | func (f XmlForest) RenderTo(r Renderer) { 682 | r.Text("xmlforest", KeywordToken) 683 | r.Text("(", SymbolToken) 684 | xmlAttributes(f).RenderTo(r) 685 | r.Text(")", SymbolToken) 686 | } 687 | 688 | type xmlAttributes []XmlAttributeEl 689 | 690 | func (attrs xmlAttributes) RenderTo(r Renderer) { 691 | for i, a := range attrs { 692 | a.RenderTo(r) 693 | if i+1 < len(attrs) { 694 | r.Text(",", SymbolToken) 695 | } 696 | } 697 | } 698 | 699 | type XmlParse struct { 700 | Type string 701 | Content Expr 702 | WhitespaceOption string 703 | } 704 | 705 | func (p XmlParse) RenderTo(r Renderer) { 706 | r.Text("xmlparse", KeywordToken) 707 | r.Text("(", SymbolToken) 708 | r.Text(p.Type, KeywordToken) 709 | p.Content.RenderTo(r) 710 | if p.WhitespaceOption != "" { 711 | r.Text(p.WhitespaceOption, KeywordToken) 712 | } 713 | 714 | r.Text(")", SymbolToken) 715 | } 716 | 717 | type XmlPi struct { 718 | Name string 719 | Content Expr 720 | } 721 | 722 | func (p XmlPi) RenderTo(r Renderer) { 723 | r.Text("xmlpi", KeywordToken) 724 | r.Text("(", SymbolToken) 725 | r.Text("name", KeywordToken) 726 | r.Text(p.Name, IdentifierToken) 727 | 728 | if p.Content != nil { 729 | r.Text(",", SymbolToken) 730 | p.Content.RenderTo(r) 731 | } 732 | r.Text(")", SymbolToken) 733 | } 734 | 735 | type XmlRoot struct { 736 | Xml Expr 737 | Version XmlRootVersion 738 | Standalone string 739 | } 740 | 741 | func (x XmlRoot) RenderTo(r Renderer) { 742 | r.Text("xmlroot", KeywordToken) 743 | r.Text("(", SymbolToken) 744 | x.Xml.RenderTo(r) 745 | r.Text(",", SymbolToken) 746 | x.Version.RenderTo(r) 747 | if x.Standalone != "" { 748 | r.Text(",", SymbolToken) 749 | r.Text("standalone", KeywordToken) 750 | r.Text(x.Standalone, KeywordToken) 751 | } 752 | r.Text(")", SymbolToken) 753 | } 754 | 755 | type XmlRootVersion struct { 756 | Expr Expr 757 | } 758 | 759 | func (rv XmlRootVersion) RenderTo(r Renderer) { 760 | r.Text("version", KeywordToken) 761 | if rv.Expr != nil { 762 | rv.Expr.RenderTo(r) 763 | } else { 764 | r.Text("no value", KeywordToken) 765 | } 766 | } 767 | 768 | type XmlSerialize struct { 769 | XmlType string 770 | Content Expr 771 | Type PgType 772 | } 773 | 774 | func (s XmlSerialize) RenderTo(r Renderer) { 775 | r.Text("xmlserialize", KeywordToken) 776 | r.Text("(", SymbolToken) 777 | r.Text(s.XmlType, KeywordToken) 778 | s.Content.RenderTo(r) 779 | r.Text("as", KeywordToken) 780 | s.Type.RenderTo(r) 781 | r.Text(")", SymbolToken) 782 | } 783 | 784 | type CollateExpr struct { 785 | Expr Expr 786 | Collation AnyName 787 | } 788 | 789 | func (c CollateExpr) RenderTo(r Renderer) { 790 | c.Expr.RenderTo(r) 791 | r.Text("collate", KeywordToken) 792 | c.Collation.RenderTo(r) 793 | } 794 | 795 | type NotExpr struct { 796 | Expr Expr 797 | } 798 | 799 | func (e NotExpr) RenderTo(r Renderer) { 800 | r.Text("not", KeywordToken) 801 | e.Expr.RenderTo(r) 802 | } 803 | 804 | type IsExpr struct { 805 | Expr Expr 806 | Not bool 807 | Op string // null, document, true, false, etc. 808 | } 809 | 810 | func (e IsExpr) RenderTo(r Renderer) { 811 | e.Expr.RenderTo(r) 812 | r.Text("is", KeywordToken) 813 | if e.Not { 814 | r.Text("not", KeywordToken) 815 | } 816 | r.Text(e.Op, KeywordToken) 817 | } 818 | 819 | type AliasedExpr struct { 820 | Expr Expr 821 | Alias string 822 | } 823 | 824 | func (e AliasedExpr) RenderTo(r Renderer) { 825 | e.Expr.RenderTo(r) 826 | r.Text("as", KeywordToken) 827 | r.Text(e.Alias, IdentifierToken) 828 | } 829 | 830 | type IntoClause struct { 831 | Options string 832 | OptTable bool 833 | Target AnyName 834 | } 835 | 836 | func (i IntoClause) RenderTo(r Renderer) { 837 | r.Text("into", KeywordToken) 838 | 839 | if i.Options != "" { 840 | r.Text(i.Options, KeywordToken) 841 | } 842 | 843 | if i.OptTable { 844 | r.Text("table", KeywordToken) 845 | } 846 | 847 | i.Target.RenderTo(r) 848 | r.Control(NewLineToken) 849 | } 850 | 851 | type FromClause struct { 852 | Expr Expr 853 | } 854 | 855 | func (e FromClause) RenderTo(r Renderer) { 856 | r.Text("from", KeywordToken) 857 | r.Control(NewLineToken) 858 | r.Control(IndentToken) 859 | e.Expr.RenderTo(r) 860 | r.Control(NewLineToken) 861 | r.Control(UnindentToken) 862 | } 863 | 864 | type JoinExpr struct { 865 | Left Expr 866 | Join string 867 | Right Expr 868 | Using []string 869 | On Expr 870 | } 871 | 872 | func (s JoinExpr) RenderTo(r Renderer) { 873 | s.Left.RenderTo(r) 874 | 875 | if s.Join == "," { 876 | r.Text(",", SymbolToken) 877 | r.Control(NewLineToken) 878 | } else { 879 | r.Control(NewLineToken) 880 | r.Text(s.Join, KeywordToken) 881 | } 882 | 883 | s.Right.RenderTo(r) 884 | 885 | if len(s.Using) > 0 { 886 | r.Text("using", KeywordToken) 887 | r.Text("(", SymbolToken) 888 | 889 | for i, u := range s.Using { 890 | r.Text(u, IdentifierToken) 891 | if i+1 < len(s.Using) { 892 | r.Text(",", SymbolToken) 893 | } 894 | } 895 | 896 | r.Text(")", SymbolToken) 897 | } 898 | 899 | if s.On != nil { 900 | r.Text("on", KeywordToken) 901 | s.On.RenderTo(r) 902 | } 903 | } 904 | 905 | type WhereClause struct { 906 | Expr Expr 907 | } 908 | 909 | func (e WhereClause) RenderTo(r Renderer) { 910 | r.Text("where", KeywordToken) 911 | r.Control(NewLineToken) 912 | r.Control(IndentToken) 913 | e.Expr.RenderTo(r) 914 | r.Control(NewLineToken) 915 | r.Control(UnindentToken) 916 | } 917 | 918 | type OrderExpr struct { 919 | Expr Expr 920 | Order string 921 | Using AnyName 922 | Nulls string 923 | } 924 | 925 | func (e OrderExpr) RenderTo(r Renderer) { 926 | e.Expr.RenderTo(r) 927 | if e.Order != "" { 928 | r.Text(e.Order, KeywordToken) 929 | } 930 | if len(e.Using) > 0 { 931 | r.Text("using", KeywordToken) 932 | e.Using.RenderTo(r) 933 | } 934 | if e.Nulls != "" { 935 | r.Text("nulls", KeywordToken) 936 | r.Text(e.Nulls, KeywordToken) 937 | } 938 | } 939 | 940 | type OrderClause struct { 941 | Exprs []OrderExpr 942 | } 943 | 944 | func (e OrderClause) RenderTo(r Renderer) { 945 | r.Text("order by", KeywordToken) 946 | r.Control(NewLineToken) 947 | r.Control(IndentToken) 948 | 949 | for i, f := range e.Exprs { 950 | f.RenderTo(r) 951 | if i < len(e.Exprs)-1 { 952 | r.Text(",", SymbolToken) 953 | } 954 | r.Control(NewLineToken) 955 | } 956 | r.Control(UnindentToken) 957 | } 958 | 959 | type GroupByClause struct { 960 | Exprs []Expr 961 | } 962 | 963 | func (e GroupByClause) RenderTo(r Renderer) { 964 | r.Text("group by", KeywordToken) 965 | r.Control(NewLineToken) 966 | r.Control(IndentToken) 967 | 968 | for i, f := range e.Exprs { 969 | f.RenderTo(r) 970 | if i < len(e.Exprs)-1 { 971 | r.Text(",", SymbolToken) 972 | } 973 | r.Control(NewLineToken) 974 | } 975 | r.Control(UnindentToken) 976 | } 977 | 978 | type LimitClause struct { 979 | Limit Expr 980 | Offset Expr 981 | } 982 | 983 | func (e LimitClause) RenderTo(r Renderer) { 984 | if e.Limit != nil { 985 | r.Text("limit", KeywordToken) 986 | e.Limit.RenderTo(r) 987 | r.Control(NewLineToken) 988 | } 989 | if e.Offset != nil { 990 | r.Text("offset", KeywordToken) 991 | e.Offset.RenderTo(r) 992 | r.Control(NewLineToken) 993 | } 994 | } 995 | 996 | type AtTimeZoneExpr struct { 997 | Expr Expr 998 | TimeZone Expr 999 | } 1000 | 1001 | func (e AtTimeZoneExpr) RenderTo(r Renderer) { 1002 | e.Expr.RenderTo(r) 1003 | r.Text("at time zone", KeywordToken) 1004 | e.TimeZone.RenderTo(r) 1005 | } 1006 | 1007 | type LockingItem struct { 1008 | Strength string 1009 | LockedRels []AnyName 1010 | WaitPolicy string 1011 | } 1012 | 1013 | func (li LockingItem) RenderTo(r Renderer) { 1014 | r.Text("for", KeywordToken) 1015 | r.Text(li.Strength, KeywordToken) 1016 | 1017 | if li.LockedRels != nil { 1018 | r.Text("of", KeywordToken) 1019 | 1020 | for i, lr := range li.LockedRels { 1021 | lr.RenderTo(r) 1022 | if i < len(li.LockedRels)-1 { 1023 | r.Text(",", SymbolToken) 1024 | } 1025 | } 1026 | } 1027 | 1028 | if li.WaitPolicy != "" { 1029 | r.Text(li.WaitPolicy, KeywordToken) 1030 | } 1031 | 1032 | r.Control(NewLineToken) 1033 | } 1034 | 1035 | type LockingClause struct { 1036 | Locks []LockingItem 1037 | } 1038 | 1039 | func (lc LockingClause) RenderTo(r Renderer) { 1040 | for _, li := range lc.Locks { 1041 | li.RenderTo(r) 1042 | } 1043 | } 1044 | 1045 | type FuncExprNoParens string 1046 | 1047 | func (fe FuncExprNoParens) RenderTo(r Renderer) { 1048 | r.Text(string(fe), KeywordToken) 1049 | } 1050 | 1051 | type FuncExpr struct { 1052 | FuncApplication 1053 | WithinGroupClause *WithinGroupClause 1054 | FilterClause *FilterClause 1055 | OverClause *OverClause 1056 | } 1057 | 1058 | func (fe FuncExpr) RenderTo(r Renderer) { 1059 | fe.FuncApplication.RenderTo(r) 1060 | 1061 | if fe.WithinGroupClause != nil { 1062 | tr := &TokenRenderer{} 1063 | fe.WithinGroupClause.RenderTo(tr) 1064 | tokens := TryOneLine([]RenderToken(*tr), 60) 1065 | RenderTokens(r, tokens) 1066 | } 1067 | 1068 | if fe.FilterClause != nil { 1069 | fe.FilterClause.RenderTo(r) 1070 | } 1071 | 1072 | if fe.OverClause != nil { 1073 | fe.OverClause.RenderTo(r) 1074 | } 1075 | } 1076 | 1077 | type FuncApplication struct { 1078 | Name AnyName 1079 | 1080 | Distinct bool 1081 | 1082 | Star bool 1083 | Args []FuncArg 1084 | VariadicArg *FuncArg 1085 | 1086 | OrderClause *OrderClause 1087 | } 1088 | 1089 | func (fa FuncApplication) RenderTo(r Renderer) { 1090 | fa.Name.RenderTo(r) 1091 | r.Text("(", SymbolToken) 1092 | 1093 | if fa.Distinct { 1094 | r.Text("distinct", KeywordToken) 1095 | } 1096 | 1097 | if fa.Star { 1098 | r.Text("*", SymbolToken) 1099 | } else if len(fa.Args) > 0 { 1100 | for i, a := range fa.Args { 1101 | a.RenderTo(r) 1102 | if i < len(fa.Args)-1 { 1103 | r.Text(",", SymbolToken) 1104 | } 1105 | } 1106 | } 1107 | 1108 | if fa.VariadicArg != nil { 1109 | if len(fa.Args) > 0 { 1110 | r.Text(",", SymbolToken) 1111 | } 1112 | 1113 | r.Text("variadic", KeywordToken) 1114 | fa.VariadicArg.RenderTo(r) 1115 | } 1116 | 1117 | if fa.OrderClause != nil { 1118 | tr := &TokenRenderer{} 1119 | fa.OrderClause.RenderTo(tr) 1120 | tokens := TryOneLine([]RenderToken(*tr), 60) 1121 | RenderTokens(r, tokens) 1122 | } 1123 | 1124 | r.Text(")", SymbolToken) 1125 | } 1126 | 1127 | type FuncArg struct { 1128 | Name string 1129 | NameOp string 1130 | Expr Expr 1131 | } 1132 | 1133 | func (fa FuncArg) RenderTo(r Renderer) { 1134 | if fa.Name != "" { 1135 | r.Text(fa.Name, IdentifierToken) 1136 | r.Text(fa.NameOp, SymbolToken) 1137 | } 1138 | fa.Expr.RenderTo(r) 1139 | } 1140 | 1141 | type CastFunc struct { 1142 | Name string 1143 | Expr Expr 1144 | Type PgType 1145 | } 1146 | 1147 | func (cf CastFunc) RenderTo(r Renderer) { 1148 | r.Text(cf.Name, KeywordToken) 1149 | r.Text("(", SymbolToken) 1150 | cf.Expr.RenderTo(r) 1151 | r.Text("as", KeywordToken) 1152 | cf.Type.RenderTo(r) 1153 | r.Text(")", SymbolToken) 1154 | } 1155 | 1156 | type IsOfExpr struct { 1157 | Expr Expr 1158 | Not bool 1159 | Types []PgType 1160 | } 1161 | 1162 | func (io IsOfExpr) RenderTo(r Renderer) { 1163 | io.Expr.RenderTo(r) 1164 | r.Text("is", KeywordToken) 1165 | 1166 | if io.Not { 1167 | r.Text("not", KeywordToken) 1168 | } 1169 | 1170 | r.Text("of", KeywordToken) 1171 | r.Control(SpaceToken) 1172 | r.Text("(", SymbolToken) 1173 | 1174 | for i, t := range io.Types { 1175 | t.RenderTo(r) 1176 | 1177 | if i < len(io.Types)-1 { 1178 | r.Text(",", SymbolToken) 1179 | } 1180 | } 1181 | 1182 | r.Text(")", SymbolToken) 1183 | } 1184 | 1185 | type WithinGroupClause OrderClause 1186 | 1187 | func (w WithinGroupClause) RenderTo(r Renderer) { 1188 | r.Text("within group", KeywordToken) 1189 | r.Control(SpaceToken) 1190 | r.Text("(", SymbolToken) 1191 | OrderClause(w).RenderTo(r) 1192 | r.Text(")", SymbolToken) 1193 | } 1194 | 1195 | type FilterClause struct { 1196 | Expr 1197 | } 1198 | 1199 | func (f FilterClause) RenderTo(r Renderer) { 1200 | r.Text("filter", KeywordToken) 1201 | r.Control(SpaceToken) 1202 | r.Text("(", SymbolToken) 1203 | r.Text("where", KeywordToken) 1204 | f.Expr.RenderTo(r) 1205 | r.Text(")", SymbolToken) 1206 | } 1207 | 1208 | type DefaultExpr bool 1209 | 1210 | func (d DefaultExpr) RenderTo(r Renderer) { 1211 | r.Text("default", KeywordToken) 1212 | } 1213 | 1214 | type Row struct { 1215 | RowWord bool 1216 | Exprs []Expr 1217 | } 1218 | 1219 | func (row Row) RenderTo(r Renderer) { 1220 | if row.RowWord { 1221 | r.Text("row", KeywordToken) 1222 | } 1223 | 1224 | r.Text("(", SymbolToken) 1225 | 1226 | for i, e := range row.Exprs { 1227 | e.RenderTo(r) 1228 | if i < len(row.Exprs)-1 { 1229 | r.Text(",", SymbolToken) 1230 | } 1231 | } 1232 | 1233 | r.Text(")", SymbolToken) 1234 | } 1235 | 1236 | type ValuesRow []Expr 1237 | 1238 | func (vr ValuesRow) RenderTo(r Renderer) { 1239 | r.Text("(", SymbolToken) 1240 | 1241 | for i, e := range vr { 1242 | e.RenderTo(r) 1243 | if i < len(vr)-1 { 1244 | r.Text(",", SymbolToken) 1245 | } 1246 | } 1247 | 1248 | r.Text(")", SymbolToken) 1249 | } 1250 | 1251 | type ValuesClause []ValuesRow 1252 | 1253 | func (vc ValuesClause) RenderTo(r Renderer) { 1254 | r.Text("values", KeywordToken) 1255 | r.Control(NewLineToken) 1256 | r.Control(IndentToken) 1257 | 1258 | for i, row := range vc { 1259 | row.RenderTo(r) 1260 | if i < len(vc)-1 { 1261 | r.Text(",", SymbolToken) 1262 | } 1263 | r.Control(NewLineToken) 1264 | } 1265 | 1266 | r.Control(UnindentToken) 1267 | } 1268 | 1269 | type OverClause struct { 1270 | Name string 1271 | Specification *WindowSpecification 1272 | } 1273 | 1274 | func (oc *OverClause) RenderTo(r Renderer) { 1275 | r.Text("over", KeywordToken) 1276 | r.Control(SpaceToken) 1277 | if oc.Name != "" { 1278 | r.Text(oc.Name, IdentifierToken) 1279 | } else { 1280 | oc.Specification.RenderTo(r) 1281 | } 1282 | } 1283 | 1284 | type WindowClause []WindowDefinition 1285 | 1286 | func (wc WindowClause) RenderTo(r Renderer) { 1287 | r.Text("window", KeywordToken) 1288 | r.Control(NewLineToken) 1289 | r.Control(IndentToken) 1290 | 1291 | for i, wd := range wc { 1292 | wd.RenderTo(r) 1293 | if i < len(wc)-1 { 1294 | r.Text(",", SymbolToken) 1295 | } 1296 | r.Control(NewLineToken) 1297 | } 1298 | 1299 | r.Control(UnindentToken) 1300 | } 1301 | 1302 | type WindowDefinition struct { 1303 | Name string 1304 | Specification WindowSpecification 1305 | } 1306 | 1307 | func (wd WindowDefinition) RenderTo(r Renderer) { 1308 | r.Text(wd.Name, IdentifierToken) 1309 | r.Text("as", KeywordToken) 1310 | r.Control(SpaceToken) 1311 | wd.Specification.RenderTo(r) 1312 | } 1313 | 1314 | type WindowSpecification struct { 1315 | ExistingName string 1316 | PartitionClause PartitionClause 1317 | OrderClause *OrderClause 1318 | FrameClause *FrameClause 1319 | } 1320 | 1321 | func (ws WindowSpecification) RenderTo(r Renderer) { 1322 | r.Text("(", SymbolToken) 1323 | 1324 | if ws.ExistingName != "" { 1325 | r.Text(ws.ExistingName, IdentifierToken) 1326 | } 1327 | 1328 | if ws.PartitionClause != nil { 1329 | ws.PartitionClause.RenderTo(r) 1330 | } 1331 | 1332 | if ws.OrderClause != nil { 1333 | tr := &TokenRenderer{} 1334 | ws.OrderClause.RenderTo(tr) 1335 | tokens := TryOneLine([]RenderToken(*tr), 60) 1336 | RenderTokens(r, tokens) 1337 | } 1338 | 1339 | if ws.FrameClause != nil { 1340 | ws.FrameClause.RenderTo(r) 1341 | } 1342 | 1343 | r.Text(")", SymbolToken) 1344 | } 1345 | 1346 | type PartitionClause []Expr 1347 | 1348 | func (pc PartitionClause) RenderTo(r Renderer) { 1349 | r.Text("partition by", KeywordToken) 1350 | 1351 | for i, e := range pc { 1352 | e.RenderTo(r) 1353 | if i < len(pc)-1 { 1354 | r.Text(",", SymbolToken) 1355 | } 1356 | } 1357 | } 1358 | 1359 | type FrameClause struct { 1360 | Mode string 1361 | Start *FrameBound 1362 | End *FrameBound 1363 | } 1364 | 1365 | func (fc *FrameClause) RenderTo(r Renderer) { 1366 | r.Text(fc.Mode, KeywordToken) 1367 | 1368 | if fc.End != nil { 1369 | r.Text("between", KeywordToken) 1370 | fc.Start.RenderTo(r) 1371 | r.Text("and", KeywordToken) 1372 | fc.End.RenderTo(r) 1373 | } else { 1374 | fc.Start.RenderTo(r) 1375 | } 1376 | } 1377 | 1378 | type FrameBound struct { 1379 | CurrentRow bool 1380 | 1381 | BoundExpr Expr 1382 | Direction string 1383 | } 1384 | 1385 | func (fb FrameBound) RenderTo(r Renderer) { 1386 | if fb.CurrentRow { 1387 | r.Text("current row", KeywordToken) 1388 | return 1389 | } 1390 | 1391 | if fb.BoundExpr != nil { 1392 | fb.BoundExpr.RenderTo(r) 1393 | } else { 1394 | r.Text("unbounded", KeywordToken) 1395 | } 1396 | 1397 | r.Text(fb.Direction, KeywordToken) 1398 | } 1399 | 1400 | type RelationExpr struct { 1401 | Name AnyName 1402 | Star bool 1403 | Only bool 1404 | } 1405 | 1406 | func (re RelationExpr) RenderTo(r Renderer) { 1407 | if re.Only { 1408 | r.Text("only", KeywordToken) 1409 | } 1410 | 1411 | re.Name.RenderTo(r) 1412 | 1413 | if re.Star { 1414 | r.Text("*", SymbolToken) 1415 | } 1416 | 1417 | r.Control(NewLineToken) 1418 | } 1419 | 1420 | type SimpleSelect struct { 1421 | DistinctList []Expr 1422 | TargetList []Expr 1423 | IntoClause *IntoClause 1424 | FromClause *FromClause 1425 | WhereClause *WhereClause 1426 | GroupByClause *GroupByClause 1427 | HavingClause Expr 1428 | WindowClause WindowClause 1429 | 1430 | ValuesClause ValuesClause 1431 | 1432 | LeftSelect *SelectStmt 1433 | SetOp string 1434 | SetAll bool 1435 | RightSelect *SelectStmt 1436 | 1437 | Table *RelationExpr 1438 | } 1439 | 1440 | func (s SimpleSelect) RenderTo(r Renderer) { 1441 | if s.Table != nil { 1442 | r.Text("table", KeywordToken) 1443 | s.Table.RenderTo(r) 1444 | return 1445 | } 1446 | 1447 | if s.ValuesClause != nil { 1448 | s.ValuesClause.RenderTo(r) 1449 | return 1450 | } 1451 | 1452 | if s.LeftSelect != nil { 1453 | s.LeftSelect.RenderTo(r) 1454 | r.Control(NewLineToken) 1455 | r.Text(s.SetOp, KeywordToken) 1456 | 1457 | if s.SetAll { 1458 | r.Text("all", KeywordToken) 1459 | } 1460 | 1461 | r.Control(NewLineToken) 1462 | 1463 | s.RightSelect.RenderTo(r) 1464 | 1465 | return 1466 | } 1467 | 1468 | r.Text("select", KeywordToken) 1469 | 1470 | if s.DistinctList != nil { 1471 | r.Text("distinct", KeywordToken) 1472 | 1473 | if len(s.DistinctList) > 0 { 1474 | r.Text("on", KeywordToken) 1475 | r.Text("(", SymbolToken) 1476 | 1477 | for i, f := range s.DistinctList { 1478 | f.RenderTo(r) 1479 | if i < len(s.DistinctList)-1 { 1480 | r.Text(",", SymbolToken) 1481 | } 1482 | } 1483 | r.Text(")", SymbolToken) 1484 | } 1485 | 1486 | } 1487 | 1488 | r.Control(NewLineToken) 1489 | r.Control(IndentToken) 1490 | for i, f := range s.TargetList { 1491 | f.RenderTo(r) 1492 | if i < len(s.TargetList)-1 { 1493 | r.Text(",", SymbolToken) 1494 | } 1495 | r.Control(NewLineToken) 1496 | } 1497 | r.Control(UnindentToken) 1498 | 1499 | if s.IntoClause != nil { 1500 | s.IntoClause.RenderTo(r) 1501 | } 1502 | 1503 | if s.FromClause != nil { 1504 | s.FromClause.RenderTo(r) 1505 | } 1506 | 1507 | if s.WhereClause != nil { 1508 | s.WhereClause.RenderTo(r) 1509 | } 1510 | 1511 | if s.GroupByClause != nil { 1512 | s.GroupByClause.RenderTo(r) 1513 | } 1514 | 1515 | if s.HavingClause != nil { 1516 | r.Text("having", KeywordToken) 1517 | r.Control(NewLineToken) 1518 | r.Control(IndentToken) 1519 | s.HavingClause.RenderTo(r) 1520 | r.Control(NewLineToken) 1521 | } 1522 | 1523 | if s.WindowClause != nil { 1524 | s.WindowClause.RenderTo(r) 1525 | } 1526 | } 1527 | 1528 | type SelectStmt struct { 1529 | SimpleSelect 1530 | OrderClause *OrderClause 1531 | LimitClause *LimitClause 1532 | LockingClause *LockingClause 1533 | 1534 | ParenWrapped bool 1535 | Semicolon bool 1536 | } 1537 | 1538 | func (s SelectStmt) RenderTo(r Renderer) { 1539 | if s.ParenWrapped { 1540 | r.Text("(", SymbolToken) 1541 | } 1542 | 1543 | s.SimpleSelect.RenderTo(r) 1544 | 1545 | if s.OrderClause != nil { 1546 | s.OrderClause.RenderTo(r) 1547 | } 1548 | 1549 | if s.LimitClause != nil { 1550 | s.LimitClause.RenderTo(r) 1551 | } 1552 | 1553 | if s.LockingClause != nil { 1554 | s.LockingClause.RenderTo(r) 1555 | } 1556 | 1557 | if s.ParenWrapped { 1558 | r.Text(")", SymbolToken) 1559 | r.Control(NewLineToken) 1560 | } 1561 | 1562 | if s.Semicolon { 1563 | r.Text(";", SymbolToken) 1564 | r.Control(NewLineToken) 1565 | } 1566 | } 1567 | 1568 | type ExistsExpr SelectStmt 1569 | 1570 | func (e ExistsExpr) RenderTo(r Renderer) { 1571 | r.Text("exists", KeywordToken) 1572 | 1573 | SelectStmt(e).RenderTo(r) 1574 | } 1575 | 1576 | type ArraySubselect SelectStmt 1577 | 1578 | func (a ArraySubselect) RenderTo(r Renderer) { 1579 | r.Text("array", KeywordToken) 1580 | 1581 | SelectStmt(a).RenderTo(r) 1582 | } 1583 | -------------------------------------------------------------------------------- /renderer.go: -------------------------------------------------------------------------------- 1 | package sqlfmt 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | NullToken = iota 10 | KeywordToken = iota 11 | IdentifierToken = iota 12 | SymbolToken = iota 13 | ConstantToken = iota 14 | SpaceToken = iota 15 | RefuseSpaceToken = iota 16 | NewLineToken = iota 17 | IndentToken = iota 18 | UnindentToken = iota 19 | ) 20 | 21 | type RenderToken struct { 22 | Type int 23 | Value string 24 | } 25 | 26 | type Renderer interface { 27 | Text(val string, typ int) 28 | Control(typ int) 29 | } 30 | 31 | type TextRenderer struct { 32 | w io.Writer 33 | err error 34 | indentLvl int 35 | indent string 36 | lineIndented bool 37 | newLine bool 38 | lastRenderToken RenderToken 39 | 40 | UpperCase bool 41 | } 42 | 43 | func (left RenderToken) SpaceBetween(right RenderToken) bool { 44 | if left.Type == RefuseSpaceToken { 45 | return false 46 | } 47 | 48 | switch left.Type { 49 | case KeywordToken, IdentifierToken, ConstantToken: 50 | switch right.Type { 51 | case KeywordToken, IdentifierToken, ConstantToken: 52 | return true 53 | case SymbolToken: 54 | switch right.Value { 55 | case "[", "(", "]", ")", ".", ",", "::", ":": 56 | return false 57 | } 58 | return true 59 | } 60 | case SymbolToken: 61 | switch left.Value { 62 | case ".", "(", "[", "::", ":": 63 | return false 64 | } 65 | 66 | if right.Type == NewLineToken { 67 | return false 68 | } 69 | 70 | if left.Value == "," { 71 | return true 72 | } 73 | 74 | if right.Type == SymbolToken { 75 | switch right.Value { 76 | case ".", "(", "[", "::", ")", "]", ",", ":": 77 | return false 78 | } 79 | } 80 | 81 | return true 82 | } 83 | 84 | return false 85 | } 86 | 87 | func NewTextRenderer(w io.Writer) *TextRenderer { 88 | return &TextRenderer{w: w, indent: " "} 89 | } 90 | 91 | func (tr *TextRenderer) Text(val string, tokenType int) { 92 | if !tr.lineIndented { 93 | for i := 0; i < tr.indentLvl; i++ { 94 | _, tr.err = io.WriteString(tr.w, tr.indent) 95 | if tr.err != nil { 96 | return 97 | } 98 | } 99 | 100 | tr.lineIndented = true 101 | } 102 | 103 | token := RenderToken{Type: tokenType, Value: val} 104 | 105 | if tr.newLine { 106 | tr.newLine = false 107 | } else if tr.lastRenderToken.SpaceBetween(token) { 108 | _, tr.err = io.WriteString(tr.w, " ") 109 | if tr.err != nil { 110 | return 111 | } 112 | } 113 | 114 | tr.lastRenderToken = token 115 | 116 | if tr.UpperCase { 117 | if tokenType == KeywordToken || tokenType == SymbolToken { 118 | val = strings.ToUpper(val) 119 | } 120 | } 121 | 122 | _, tr.err = io.WriteString(tr.w, val) 123 | } 124 | 125 | func (tr *TextRenderer) Control(typ int) { 126 | if tr.err != nil { 127 | return 128 | } 129 | 130 | switch typ { 131 | case SpaceToken: 132 | _, tr.err = io.WriteString(tr.w, " ") 133 | case NewLineToken: 134 | tr.renderNewLine() 135 | case IndentToken: 136 | tr.indentLvl = tr.indentLvl + 1 137 | case UnindentToken: 138 | tr.indentLvl = tr.indentLvl - 1 139 | } 140 | 141 | tr.lastRenderToken = RenderToken{Type: typ} 142 | } 143 | 144 | func (tr *TextRenderer) renderNewLine() { 145 | if tr.newLine { 146 | return 147 | } 148 | 149 | tr.newLine = true 150 | tr.lastRenderToken = RenderToken{Type: NewLineToken} 151 | 152 | _, tr.err = io.WriteString(tr.w, "\n") 153 | if tr.err != nil { 154 | return 155 | } 156 | 157 | tr.lineIndented = false 158 | } 159 | 160 | func (tr *TextRenderer) Error() error { 161 | return tr.err 162 | } 163 | -------------------------------------------------------------------------------- /renderer_test.go: -------------------------------------------------------------------------------- 1 | package sqlfmt 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestTextRenderer(t *testing.T) { 9 | var buf bytes.Buffer 10 | 11 | tr := NewTextRenderer(&buf) 12 | 13 | tr.Text("select", KeywordToken) 14 | tr.Control(NewLineToken) 15 | tr.Control(IndentToken) 16 | tr.Text("foo", IdentifierToken) 17 | tr.Text(",", SymbolToken) 18 | tr.Text("bar", IdentifierToken) 19 | tr.Control(NewLineToken) 20 | tr.Control(UnindentToken) 21 | tr.Text("from", KeywordToken) 22 | tr.Control(NewLineToken) 23 | tr.Control(IndentToken) 24 | tr.Text("baz", IdentifierToken) 25 | 26 | expected := `select 27 | foo, bar 28 | from 29 | baz` 30 | 31 | if buf.String() != expected { 32 | t.Errorf("Expected `%s`, got `%s`", expected, buf.String()) 33 | } 34 | } 35 | 36 | func TestTextRendererUpper(t *testing.T) { 37 | var buf bytes.Buffer 38 | 39 | tr := NewTextRenderer(&buf) 40 | tr.UpperCase = true 41 | 42 | tr.Text("select", KeywordToken) 43 | tr.Control(NewLineToken) 44 | tr.Control(IndentToken) 45 | tr.Text("foo", IdentifierToken) 46 | tr.Text(",", SymbolToken) 47 | tr.Text("bar", IdentifierToken) 48 | tr.Control(NewLineToken) 49 | tr.Control(UnindentToken) 50 | tr.Text("from", KeywordToken) 51 | tr.Control(NewLineToken) 52 | tr.Control(IndentToken) 53 | tr.Text("baz", IdentifierToken) 54 | tr.Control(NewLineToken) 55 | tr.Control(UnindentToken) 56 | tr.Text("where", KeywordToken) 57 | tr.Control(NewLineToken) 58 | tr.Control(IndentToken) 59 | tr.Text("foo", IdentifierToken) 60 | tr.Text("=", KeywordToken) 61 | tr.Text("'foo'", ConstantToken) 62 | 63 | expected := `SELECT 64 | foo, bar 65 | FROM 66 | baz 67 | WHERE 68 | foo = 'foo'` 69 | 70 | if buf.String() != expected { 71 | t.Errorf("Expected `%s`, got `%s`", expected, buf.String()) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /testdata/arithmetic_expression.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | 1 + 1, 3 | 2 - 1, 4 | 3 * 2, 5 | 8 / 2, 6 | 1 + 1 * 3, 7 | 3 + 8 / 7, 8 | 1 + 1 * 3, 9 | 312 + 8 / 7, 10 | 4 % 3, 11 | 7 ^ 5 12 | -------------------------------------------------------------------------------- /testdata/arithmetic_expression.input.sql: -------------------------------------------------------------------------------- 1 | select 1 + 1, 2 - 1, 3 * 2, 8 / 2, 2 | 1 + 1 * 3, 3 + 8 / 7, 3 | 1+1*3, 312+8/7, 4 | 4%3, 7^5 5 | -------------------------------------------------------------------------------- /testdata/array_constructor.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | array[], 3 | array[1], 4 | array[1, 2, 3, foo + bar], 5 | array[array[1, 2, 3], array[4, 5, 6]], 6 | array[[1, 2, 3], [4, 5, 6]] 7 | from 8 | baz 9 | -------------------------------------------------------------------------------- /testdata/array_constructor.input.sql: -------------------------------------------------------------------------------- 1 | select array[], array[1], array[1,2,3,foo+bar], array[array[1,2,3], array[4,5,6]], array[[1,2,3], [4,5,6]] from baz 2 | -------------------------------------------------------------------------------- /testdata/array_index.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | (array['a', 'b', 'c', foo, bar])[1], 3 | quz[42], 4 | (select 5 | array['a', 'b', 'c'] 6 | )[1] 7 | from 8 | baz 9 | -------------------------------------------------------------------------------- /testdata/array_index.input.sql: -------------------------------------------------------------------------------- 1 | select (array['a', 'b', 'c', foo, bar])[1], quz[42], 2 | (select array['a', 'b', 'c'])[1] from baz 3 | -------------------------------------------------------------------------------- /testdata/array_slice.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | (array['a', 'b', 'c', foo, bar])[1:5], 3 | quz[42:50] 4 | from 5 | baz 6 | -------------------------------------------------------------------------------- /testdata/array_slice.input.sql: -------------------------------------------------------------------------------- 1 | select (array['a', 'b', 'c', foo, bar])[1:5], quz[42:50] from baz 2 | -------------------------------------------------------------------------------- /testdata/array_subselect.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | array(select 4 | bar 5 | from 6 | quz 7 | where 8 | baz.foo = quz.foo 9 | ) 10 | from 11 | baz 12 | -------------------------------------------------------------------------------- /testdata/array_subselect.input.sql: -------------------------------------------------------------------------------- 1 | select foo, array(select bar from quz where baz.foo=quz.foo) from baz 2 | -------------------------------------------------------------------------------- /testdata/array_typecast.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | '{1,2,3}'::int[], 3 | '{{1,2}, {3,4}}'::int[][], 4 | '{{1,2}, {3,4}}'::int[][2] 5 | -------------------------------------------------------------------------------- /testdata/array_typecast.input.sql: -------------------------------------------------------------------------------- 1 | select '{1,2,3}'::int[], '{{1,2}, {3,4}}'::int[][], '{{1,2}, {3,4}}'::int[][2] 2 | -------------------------------------------------------------------------------- /testdata/at_time_zone.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | '2015-01-01 00:00:00-09'::timestamptz at time zone 'America/Chicago' 3 | -------------------------------------------------------------------------------- /testdata/at_time_zone.input.sql: -------------------------------------------------------------------------------- 1 | select '2015-01-01 00:00:00-09'::timestamptz at time zone 'America/Chicago' 2 | -------------------------------------------------------------------------------- /testdata/b_expr.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo between bexpr::text and bar, 3 | foo between -42 and bar, 4 | foo between +3 and bar, 5 | foo between 1 + 1 and bar, 6 | foo between 1 - 1 and bar, 7 | foo between 1 * 1 and bar, 8 | foo between 1 / 1 and bar, 9 | foo between 1 % 1 and bar, 10 | foo between 1 ^ 1 and bar, 11 | foo between 1 < 1 and bar, 12 | foo between 1 > 1 and bar, 13 | foo between 1 = 1 and bar, 14 | foo between 1 <= 1 and bar, 15 | foo between 1 >= 1 and bar, 16 | foo between 1 != 1 and bar, 17 | foo between 1 @> 1 and bar, 18 | foo between @1 and bar, 19 | foo is distinct from bar, 20 | foo is not distinct from bar, 21 | true is of (integer, bool), 22 | 'asdf' is not of (integer, bool), 23 | foo between 5 ! and bar, 24 | false between foo is document and bar, 25 | false between foo is not document and bar 26 | from 27 | baz 28 | -------------------------------------------------------------------------------- /testdata/b_expr.input.sql: -------------------------------------------------------------------------------- 1 | select foo between bexpr::text and bar, 2 | foo between -42 and bar, 3 | foo between +3 and bar, 4 | foo between 1+1 and bar, 5 | foo between 1-1 and bar, 6 | foo between 1*1 and bar, 7 | foo between 1/1 and bar, 8 | foo between 1%1 and bar, 9 | foo between 1^1 and bar, 10 | foo between 1<1 and bar, 11 | foo between 1>1 and bar, 12 | foo between 1=1 and bar, 13 | foo between 1<=1 and bar, 14 | foo between 1>=1 and bar, 15 | foo between 1!=1 and bar, 16 | foo between 1@>1 and bar, 17 | foo between @1 and bar, 18 | foo is distinct from bar, 19 | foo is not distinct from bar, 20 | true is of (integer, bool), 21 | 'asdf' is not of (integer, bool), 22 | foo between 5! and bar, 23 | false between foo is document and bar, 24 | false between foo is not document and bar 25 | 26 | 27 | from baz 28 | -------------------------------------------------------------------------------- /testdata/between.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo between bar and baz, 3 | foo not between bar and baz, 4 | foo between bar and baz, 5 | foo not between bar and baz, 6 | foo between symmetric bar and baz, 7 | foo not between symmetric bar and baz 8 | -------------------------------------------------------------------------------- /testdata/between.input.sql: -------------------------------------------------------------------------------- 1 | select foo between bar and baz, foo not between bar and baz, 2 | foo between asymmetric bar and baz, foo not between asymmetric bar and baz, 3 | foo between symmetric bar and baz, foo not between symmetric bar and baz 4 | -------------------------------------------------------------------------------- /testdata/bitconst.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | b'10101', 3 | x'0123456789abcdefABCDEF' 4 | -------------------------------------------------------------------------------- /testdata/bitconst.input.sql: -------------------------------------------------------------------------------- 1 | select b'10101',x'0123456789abcdefABCDEF' 2 | -------------------------------------------------------------------------------- /testdata/boolean_binary_op.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo 3 | and bar, 4 | baz 5 | or quz 6 | from 7 | t 8 | -------------------------------------------------------------------------------- /testdata/boolean_binary_op.input.sql: -------------------------------------------------------------------------------- 1 | select foo and bar, baz or quz from t 2 | -------------------------------------------------------------------------------- /testdata/boolean_not.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | not foo, 3 | not true, 4 | not false 5 | from 6 | t 7 | -------------------------------------------------------------------------------- /testdata/boolean_not.input.sql: -------------------------------------------------------------------------------- 1 | select not foo, not true, not false from t 2 | -------------------------------------------------------------------------------- /testdata/case_full.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | case 3 | when foo = bar then 4 | 7 5 | when foo > bar then 6 | 42 7 | else 8 | 1 9 | end 10 | from 11 | baz 12 | -------------------------------------------------------------------------------- /testdata/case_full.input.sql: -------------------------------------------------------------------------------- 1 | select case when foo=bar then 7 when foo>bar then 42 else 1 end from baz 2 | -------------------------------------------------------------------------------- /testdata/case_implicit.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | case foo 3 | when 4 then 4 | 'A' 5 | when 3 then 6 | 'B' 7 | else 8 | 'C' 9 | end 10 | from 11 | baz 12 | -------------------------------------------------------------------------------- /testdata/case_implicit.input.sql: -------------------------------------------------------------------------------- 1 | select case foo when 4 then 'A' when 3 then 'B' else 'C' end from baz 2 | -------------------------------------------------------------------------------- /testdata/cast_as.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | cast('{1,2,3}' as int[]) 3 | -------------------------------------------------------------------------------- /testdata/cast_as.input.sql: -------------------------------------------------------------------------------- 1 | select cast('{1,2,3}' as int[]) 2 | -------------------------------------------------------------------------------- /testdata/collate.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | 'Foo' collate "C", 3 | 'Bar' collate "en_US" 4 | -------------------------------------------------------------------------------- /testdata/collate.input.sql: -------------------------------------------------------------------------------- 1 | select 'Foo' collate "C", 'Bar' collate "en_US" 2 | -------------------------------------------------------------------------------- /testdata/collation_for.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | collation for(name) 3 | from 4 | people 5 | -------------------------------------------------------------------------------- /testdata/collation_for.input.sql: -------------------------------------------------------------------------------- 1 | select collation for (name) from people 2 | -------------------------------------------------------------------------------- /testdata/comment_beginning_single_line.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | -------------------------------------------------------------------------------- /testdata/comment_beginning_single_line.input.sql: -------------------------------------------------------------------------------- 1 | -- TODO - do not strip comments 2 | select foo, bar from baz 3 | -------------------------------------------------------------------------------- /testdata/comparison_expression.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | 1 = 1, 3 | 2 > 1, 4 | 2 < 8, 5 | 1 != 2, 6 | 1 != 2, 7 | 3 >= 2, 8 | 2 <= 7 9 | -------------------------------------------------------------------------------- /testdata/comparison_expression.input.sql: -------------------------------------------------------------------------------- 1 | select 1 = 1, 2 > 1, 2 < 8, 1!=2, 1<>2, 3>=2, 2 <= 7 2 | -------------------------------------------------------------------------------- /testdata/const_type_name.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | char 'hi', 3 | char(2) 'hi', 4 | varchar 'hi', 5 | varchar(2) 'hi', 6 | bit '1010', 7 | bit(4) '1010', 8 | varbit '1010', 9 | varbit(4) '1010', 10 | timestamp(4) '2000-01-01 00:00:00', 11 | timestamp(4) with time zone '2000-01-01 00:00:00', 12 | timestamp(4) '2000-01-01 00:00:00', 13 | timestamp '2000-01-01 00:00:00', 14 | timestamp with time zone '2000-01-01 00:00:00', 15 | timestamp '2000-01-01 00:00:00', 16 | time(4) '00:00:00', 17 | time(4) with time zone '00:00:00', 18 | time(4) '00:00:00', 19 | time '00:00:00', 20 | time with time zone '00:00:00', 21 | time '00:00:00' 22 | -------------------------------------------------------------------------------- /testdata/const_type_name.input.sql: -------------------------------------------------------------------------------- 1 | select char 'hi', char(2) 'hi', varchar 'hi', varchar(2) 'hi', 2 | bit '1010', bit(4) '1010', varbit '1010', varbit(4) '1010', 3 | timestamp(4) '2000-01-01 00:00:00', timestamp(4) with time zone '2000-01-01 00:00:00', timestamp(4) without time zone '2000-01-01 00:00:00', 4 | timestamp '2000-01-01 00:00:00', timestamp with time zone '2000-01-01 00:00:00', timestamp without time zone '2000-01-01 00:00:00', 5 | time(4) '00:00:00', time(4) with time zone '00:00:00', time(4) without time zone '00:00:00', 6 | time '00:00:00', time with time zone '00:00:00', time without time zone '00:00:00' 7 | -------------------------------------------------------------------------------- /testdata/custom_operators.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo @> bar, 3 | @foo, 4 | 'foo' || 'bar' 5 | -------------------------------------------------------------------------------- /testdata/custom_operators.input.sql: -------------------------------------------------------------------------------- 1 | select foo @> bar, @foo, 'foo' || 'bar' 2 | -------------------------------------------------------------------------------- /testdata/distinct.golden.sql: -------------------------------------------------------------------------------- 1 | select distinct 2 | foo, 3 | bar 4 | from 5 | baz 6 | -------------------------------------------------------------------------------- /testdata/distinct.input.sql: -------------------------------------------------------------------------------- 1 | select distinct foo, bar from baz 2 | -------------------------------------------------------------------------------- /testdata/distinct_on.golden.sql: -------------------------------------------------------------------------------- 1 | select distinct on(foo) 2 | foo, 3 | bar 4 | from 5 | baz 6 | order by 7 | foo 8 | -------------------------------------------------------------------------------- /testdata/distinct_on.input.sql: -------------------------------------------------------------------------------- 1 | select distinct on (foo) foo, bar from baz order by foo 2 | -------------------------------------------------------------------------------- /testdata/except.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | except 7 | select 8 | a, 9 | b 10 | from 11 | quz 12 | -------------------------------------------------------------------------------- /testdata/except.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz except select a, b from quz 2 | -------------------------------------------------------------------------------- /testdata/exists.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | where 7 | exists(select 8 | 1 9 | from 10 | quz 11 | ) 12 | -------------------------------------------------------------------------------- /testdata/exists.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz where exists(select 1 from quz) 2 | -------------------------------------------------------------------------------- /testdata/extract.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | extract(year from '2000-01-01 12:34:56'::timestamptz), 3 | extract(month from '2000-01-01 12:34:56'::timestamptz), 4 | extract(day from '2000-01-01 12:34:56'::timestamptz), 5 | extract(hour from '2000-01-01 12:34:56'::timestamptz), 6 | extract(minute from '2000-01-01 12:34:56'::timestamptz), 7 | extract(second from '2000-01-01 12:34:56'::timestamptz), 8 | extract('second' from '2000-01-01 12:34:56'::timestamptz), 9 | extract("second" from '2000-01-01 12:34:56'::timestamptz) 10 | -------------------------------------------------------------------------------- /testdata/extract.input.sql: -------------------------------------------------------------------------------- 1 | select extract(year from '2000-01-01 12:34:56'::timestamptz), 2 | extract(month from '2000-01-01 12:34:56'::timestamptz), 3 | extract(day from '2000-01-01 12:34:56'::timestamptz), 4 | extract(hour from '2000-01-01 12:34:56'::timestamptz), 5 | extract(minute from '2000-01-01 12:34:56'::timestamptz), 6 | extract(second from '2000-01-01 12:34:56'::timestamptz), 7 | extract('second' from '2000-01-01 12:34:56'::timestamptz), 8 | extract("second" from '2000-01-01 12:34:56'::timestamptz) 9 | -------------------------------------------------------------------------------- /testdata/float_constant.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | 3.14 3 | -------------------------------------------------------------------------------- /testdata/float_constant.input.sql: -------------------------------------------------------------------------------- 1 | select 3.14 2 | -------------------------------------------------------------------------------- /testdata/func_expr_expr_list.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | coalesce(a, b, c), 3 | greatest(d, e, f), 4 | least(g, h, i), 5 | xmlconcat(j, k, l) 6 | from 7 | foo 8 | -------------------------------------------------------------------------------- /testdata/func_expr_expr_list.input.sql: -------------------------------------------------------------------------------- 1 | select coalesce(a,b,c), greatest(d,e,f), least(g,h,i), xmlconcat(j,k,l) from foo 2 | -------------------------------------------------------------------------------- /testdata/func_expr_no_parens.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | current_date, 3 | current_time, 4 | current_timestamp, 5 | localtime, 6 | localtimestamp, 7 | current_role, 8 | current_user, 9 | session_user, 10 | user, 11 | current_catalog, 12 | current_schema 13 | -------------------------------------------------------------------------------- /testdata/func_expr_no_parens.input.sql: -------------------------------------------------------------------------------- 1 | select current_date, current_time, current_timestamp, 2 | localtime, localtimestamp, current_role, current_user, 3 | session_user, user, current_catalog, current_schema 4 | -------------------------------------------------------------------------------- /testdata/func_expr_one_arg.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | current_time(2), 3 | current_timestamp(2), 4 | localtime(2), 5 | localtimestamp(2) 6 | -------------------------------------------------------------------------------- /testdata/func_expr_one_arg.input.sql: -------------------------------------------------------------------------------- 1 | select current_time(2), current_timestamp(2), 2 | localtime(2), localtimestamp(2) 3 | -------------------------------------------------------------------------------- /testdata/function_call_qualified.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo.quz(bar) 3 | from 4 | baz 5 | -------------------------------------------------------------------------------- /testdata/function_call_qualified.input.sql: -------------------------------------------------------------------------------- 1 | select foo.quz(bar) from baz 2 | -------------------------------------------------------------------------------- /testdata/function_call_variadic.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo(variadic array[1, 2, 3]), 3 | bar(1, 2, variadic array[3, 4, 5]) 4 | -------------------------------------------------------------------------------- /testdata/function_call_variadic.input.sql: -------------------------------------------------------------------------------- 1 | select foo(variadic array[1,2,3]), bar(1, 2, variadic array[3,4,5]) 2 | -------------------------------------------------------------------------------- /testdata/function_call_with_all.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | name, 3 | array_agg(foo) 4 | from 5 | baz 6 | group by 7 | name 8 | -------------------------------------------------------------------------------- /testdata/function_call_with_all.input.sql: -------------------------------------------------------------------------------- 1 | select name, array_agg(all foo) from baz group by name 2 | -------------------------------------------------------------------------------- /testdata/function_call_with_distinct.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | name, 3 | array_agg(distinct foo) 4 | from 5 | baz 6 | group by 7 | name 8 | -------------------------------------------------------------------------------- /testdata/function_call_with_distinct.input.sql: -------------------------------------------------------------------------------- 1 | select name, array_agg(distinct foo) from baz group by name 2 | -------------------------------------------------------------------------------- /testdata/function_call_with_filter.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | name, 3 | array_agg(foo) filter (where a = b) 4 | from 5 | baz 6 | group by 7 | name 8 | -------------------------------------------------------------------------------- /testdata/function_call_with_filter.input.sql: -------------------------------------------------------------------------------- 1 | select name, array_agg(foo) filter (where a=b) from baz group by name 2 | -------------------------------------------------------------------------------- /testdata/function_call_with_grouping_set.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | percentile_disc(0.25) within group (order by n) 3 | from 4 | generate_series(1, 10) as n 5 | -------------------------------------------------------------------------------- /testdata/function_call_with_grouping_set.input.sql: -------------------------------------------------------------------------------- 1 | select percentile_disc(0.25) within group (order by n) from generate_series(1,10) n 2 | -------------------------------------------------------------------------------- /testdata/function_call_with_order.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | name, 3 | array_agg(foo order by bar) 4 | from 5 | baz 6 | group by 7 | name 8 | -------------------------------------------------------------------------------- /testdata/function_call_with_order.input.sql: -------------------------------------------------------------------------------- 1 | select name, array_agg(foo order by bar) from baz group by name 2 | -------------------------------------------------------------------------------- /testdata/function_call_with_pg_named_args.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | quz(foo := 1, bar := 2) 3 | from 4 | baz 5 | -------------------------------------------------------------------------------- /testdata/function_call_with_pg_named_args.input.sql: -------------------------------------------------------------------------------- 1 | select quz(foo:=1,bar:=2) from baz 2 | -------------------------------------------------------------------------------- /testdata/function_call_with_positional_args.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | quz(foo, bar) 3 | from 4 | baz 5 | -------------------------------------------------------------------------------- /testdata/function_call_with_positional_args.input.sql: -------------------------------------------------------------------------------- 1 | select quz(foo,bar) from baz 2 | -------------------------------------------------------------------------------- /testdata/function_call_with_sql_named_args.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | quz(foo => 1, bar => 2) 3 | from 4 | baz 5 | -------------------------------------------------------------------------------- /testdata/function_call_with_sql_named_args.input.sql: -------------------------------------------------------------------------------- 1 | select quz(foo=>1,bar=>2) from baz 2 | -------------------------------------------------------------------------------- /testdata/function_call_with_star_arg.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | count(*) 4 | from 5 | bar 6 | group by 7 | foo 8 | -------------------------------------------------------------------------------- /testdata/function_call_with_star_arg.input.sql: -------------------------------------------------------------------------------- 1 | select foo, count(*) from bar group by foo 2 | -------------------------------------------------------------------------------- /testdata/function_call_without_args.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | now() 3 | -------------------------------------------------------------------------------- /testdata/function_call_without_args.input.sql: -------------------------------------------------------------------------------- 1 | select now() 2 | -------------------------------------------------------------------------------- /testdata/group_by.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | group by 7 | foo, 8 | bar 9 | -------------------------------------------------------------------------------- /testdata/group_by.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz group by foo, bar 2 | -------------------------------------------------------------------------------- /testdata/having.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | group by 7 | foo, 8 | bar 9 | having 10 | foo > 42 11 | -------------------------------------------------------------------------------- /testdata/having.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz group by foo, bar having foo > 42 2 | -------------------------------------------------------------------------------- /testdata/in.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | 2 in (1, 2, 3), 3 | 2 not in (1, 2, 3), 4 | 2 in (select 5 | generate_series(1, 10) 6 | ) 7 | , 8 | 2 not in (select 9 | generate_series(1, 10) 10 | ) 11 | -------------------------------------------------------------------------------- /testdata/in.input.sql: -------------------------------------------------------------------------------- 1 | select 2 in (1,2,3), 2 not in (1,2,3), 2 | 2 in (select generate_series(1,10)), 2 not in (select generate_series(1,10)) 3 | -------------------------------------------------------------------------------- /testdata/intersect.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | intersect 7 | select 8 | a, 9 | b 10 | from 11 | quz 12 | -------------------------------------------------------------------------------- /testdata/intersect.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz intersect select a, b from quz 2 | -------------------------------------------------------------------------------- /testdata/interval.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | interval '5', 3 | interval '5' hour, 4 | interval '5' hour to minute, 5 | interval '5' second(5), 6 | interval(2) '10.324' 7 | -------------------------------------------------------------------------------- /testdata/interval.input.sql: -------------------------------------------------------------------------------- 1 | select interval '5', interval '5' hour, interval '5' hour to minute, interval '5' second(5), 2 | interval(2) '10.324' 3 | -------------------------------------------------------------------------------- /testdata/is_bool_op.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo is true, 3 | foo is not true, 4 | foo is false, 5 | foo is not false, 6 | foo is unknown, 7 | foo is not unknown 8 | from 9 | bar 10 | -------------------------------------------------------------------------------- /testdata/is_bool_op.input.sql: -------------------------------------------------------------------------------- 1 | select foo is true, foo is not true, foo is false, foo is not false, foo is unknown, foo is not unknown from bar 2 | -------------------------------------------------------------------------------- /testdata/is_distinct_from.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo is distinct from bar, 3 | foo is not distinct from bar 4 | from 5 | bar 6 | -------------------------------------------------------------------------------- /testdata/is_distinct_from.input.sql: -------------------------------------------------------------------------------- 1 | select foo is distinct from bar, foo is not distinct from bar from bar 2 | -------------------------------------------------------------------------------- /testdata/is_document.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo is document, 3 | foo is not document 4 | from 5 | bar 6 | -------------------------------------------------------------------------------- /testdata/is_document.input.sql: -------------------------------------------------------------------------------- 1 | select foo is document, foo is not document from bar 2 | -------------------------------------------------------------------------------- /testdata/is_null.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo is null, 3 | foo is not null, 4 | foo is null, 5 | foo is not null 6 | from 7 | bar 8 | -------------------------------------------------------------------------------- /testdata/is_null.input.sql: -------------------------------------------------------------------------------- 1 | select foo is null, foo is not null, foo isnull, foo notnull from bar 2 | -------------------------------------------------------------------------------- /testdata/is_of_type_list.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | true is of (integer, bool), 3 | 'asdf' is not of (integer, bool) 4 | -------------------------------------------------------------------------------- /testdata/is_of_type_list.input.sql: -------------------------------------------------------------------------------- 1 | select true is of (integer, bool), 'asdf' is not of (integer, bool) 2 | -------------------------------------------------------------------------------- /testdata/like.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | where 7 | foo like 'abd%' 8 | or foo like 'ada%' escape '!' 9 | or foo not like 'abd%' 10 | or foo not like 'ada%' escape '!' 11 | or foo ilike 'efg%' 12 | or foo ilike 'ada%' escape '!' 13 | or foo not ilike 'efg%' 14 | or foo not ilike 'ada%' escape '!' 15 | -------------------------------------------------------------------------------- /testdata/like.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz 2 | where 3 | foo like 'abd%' or foo like 'ada%' escape '!' or 4 | foo not like 'abd%' or foo not like 'ada%' escape '!' 5 | or foo ilike 'efg%' or foo ilike 'ada%' escape '!' 6 | or foo not ilike 'efg%' or foo not ilike 'ada%' escape '!' 7 | -------------------------------------------------------------------------------- /testdata/limit.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | limit 42 7 | -------------------------------------------------------------------------------- /testdata/limit.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz limit 42 2 | -------------------------------------------------------------------------------- /testdata/limit_fetch.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | limit 42 7 | -------------------------------------------------------------------------------- /testdata/limit_fetch.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz fetch first 42 rows only 2 | -------------------------------------------------------------------------------- /testdata/limit_offset.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | limit 7 7 | offset 42 8 | -------------------------------------------------------------------------------- /testdata/limit_offset.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz limit 7 offset 42 2 | -------------------------------------------------------------------------------- /testdata/null.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | null 3 | -------------------------------------------------------------------------------- /testdata/null.input.sql: -------------------------------------------------------------------------------- 1 | select null 2 | -------------------------------------------------------------------------------- /testdata/nullif.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | nullif(1, 2) 3 | -------------------------------------------------------------------------------- /testdata/nullif.input.sql: -------------------------------------------------------------------------------- 1 | select nullif(1,2) 2 | -------------------------------------------------------------------------------- /testdata/offset.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | offset 42 7 | -------------------------------------------------------------------------------- /testdata/offset.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz offset 42 2 | -------------------------------------------------------------------------------- /testdata/offset_fetch.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | limit 7 7 | offset 42 8 | -------------------------------------------------------------------------------- /testdata/offset_fetch.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz offset 42 rows fetch next 7 rows only 2 | -------------------------------------------------------------------------------- /testdata/offset_limit.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | limit 7 7 | offset 42 8 | -------------------------------------------------------------------------------- /testdata/offset_limit.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz offset 42 limit 7 2 | -------------------------------------------------------------------------------- /testdata/order.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | order by 7 | quz 8 | -------------------------------------------------------------------------------- /testdata/order.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz order by quz 2 | -------------------------------------------------------------------------------- /testdata/order_column_num.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | order by 7 | 1 8 | -------------------------------------------------------------------------------- /testdata/order_column_num.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz order by 1 2 | -------------------------------------------------------------------------------- /testdata/order_desc.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | order by 7 | quz desc 8 | -------------------------------------------------------------------------------- /testdata/order_desc.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz order by quz desc 2 | -------------------------------------------------------------------------------- /testdata/order_multiple.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | order by 7 | foo desc, 8 | quz asc 9 | -------------------------------------------------------------------------------- /testdata/order_multiple.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz order by foo desc, quz asc 2 | -------------------------------------------------------------------------------- /testdata/order_nulls.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | order by 7 | foo desc nulls first, 8 | quz asc nulls last, 9 | abc nulls last 10 | -------------------------------------------------------------------------------- /testdata/order_nulls.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz order by foo desc nulls first, quz asc nulls last, abc nulls last 2 | -------------------------------------------------------------------------------- /testdata/order_using.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | order by 7 | quz using < 8 | -------------------------------------------------------------------------------- /testdata/order_using.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz order by quz using < 2 | -------------------------------------------------------------------------------- /testdata/overlaps.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | (date '2000-01-01', date '2000-01-31') overlaps (date '2000-01-15', date '2000-02-15') 3 | -------------------------------------------------------------------------------- /testdata/overlaps.input.sql: -------------------------------------------------------------------------------- 1 | select (date '2000-01-01', date '2000-01-31') overlaps (date '2000-01-15', date '2000-02-15') 2 | -------------------------------------------------------------------------------- /testdata/overlay.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | overlay('Taaas' placing 'ex' from 2 for 2), 3 | overlay('Taaas' placing 'ex' from 2) 4 | -------------------------------------------------------------------------------- /testdata/overlay.input.sql: -------------------------------------------------------------------------------- 1 | select overlay('Taaas' placing 'ex' from 2 for 2), 2 | overlay('Taaas' placing 'ex' from 2) 3 | -------------------------------------------------------------------------------- /testdata/paren_expression.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | (1 + 3) * 4 3 | -------------------------------------------------------------------------------- /testdata/paren_expression.input.sql: -------------------------------------------------------------------------------- 1 | select (1 + 3)*4 2 | -------------------------------------------------------------------------------- /testdata/position.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | position('og' in 'groggy') 3 | -------------------------------------------------------------------------------- /testdata/position.input.sql: -------------------------------------------------------------------------------- 1 | select position('og' in 'groggy') 2 | -------------------------------------------------------------------------------- /testdata/postfix_operator.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | 5 !, 3 | 7 ! 4 | -------------------------------------------------------------------------------- /testdata/postfix_operator.input.sql: -------------------------------------------------------------------------------- 1 | select 5 !, 7! 2 | -------------------------------------------------------------------------------- /testdata/quoted_identifier.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | "Foo Bar", 3 | "Embedded "" Quote" 4 | from 5 | baz 6 | -------------------------------------------------------------------------------- /testdata/quoted_identifier.input.sql: -------------------------------------------------------------------------------- 1 | select "Foo Bar", "Embedded "" Quote" from baz 2 | -------------------------------------------------------------------------------- /testdata/row.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | row(), 3 | row(1), 4 | row(1, 2), 5 | (1, 2, 3) 6 | -------------------------------------------------------------------------------- /testdata/row.input.sql: -------------------------------------------------------------------------------- 1 | select row (), row (1), row (1,2), (1,2,3) 2 | -------------------------------------------------------------------------------- /testdata/select_for_key_share.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | for key share 7 | -------------------------------------------------------------------------------- /testdata/select_for_key_share.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz for key share 2 | -------------------------------------------------------------------------------- /testdata/select_for_no_key_update.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | for no key update 7 | -------------------------------------------------------------------------------- /testdata/select_for_no_key_update.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz for no key update 2 | -------------------------------------------------------------------------------- /testdata/select_for_share.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | for share 7 | -------------------------------------------------------------------------------- /testdata/select_for_share.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz for share 2 | -------------------------------------------------------------------------------- /testdata/select_for_update.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | for update 7 | -------------------------------------------------------------------------------- /testdata/select_for_update.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz for update 2 | -------------------------------------------------------------------------------- /testdata/select_for_update_nowait.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | for update nowait 7 | -------------------------------------------------------------------------------- /testdata/select_for_update_nowait.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz for update nowait 2 | -------------------------------------------------------------------------------- /testdata/select_for_update_of.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | for update of baz 7 | -------------------------------------------------------------------------------- /testdata/select_for_update_of.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz for update of baz 2 | -------------------------------------------------------------------------------- /testdata/select_from_aliased.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | quz.foo, 3 | quz.bar 4 | from 5 | baz as quz 6 | -------------------------------------------------------------------------------- /testdata/select_from_aliased.input.sql: -------------------------------------------------------------------------------- 1 | select quz.foo, quz.bar from baz as quz 2 | -------------------------------------------------------------------------------- /testdata/select_from_comma_join.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz, 6 | quz 7 | -------------------------------------------------------------------------------- /testdata/select_from_comma_join.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar 2 | from baz, quz 3 | -------------------------------------------------------------------------------- /testdata/select_from_cross_join.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | cross join quz 7 | -------------------------------------------------------------------------------- /testdata/select_from_cross_join.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar 2 | from baz cross join quz 3 | -------------------------------------------------------------------------------- /testdata/select_from_join_on.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | join quz on baz.a = quz.b 7 | -------------------------------------------------------------------------------- /testdata/select_from_join_on.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar 2 | from baz join quz on baz.a = quz.b 3 | -------------------------------------------------------------------------------- /testdata/select_from_join_using.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | join quz using(id) 7 | -------------------------------------------------------------------------------- /testdata/select_from_join_using.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar 2 | from baz join quz using(id) 3 | -------------------------------------------------------------------------------- /testdata/select_from_join_using_multiple.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | join quz using(foo, bar) 7 | -------------------------------------------------------------------------------- /testdata/select_from_join_using_multiple.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar 2 | from baz join quz using(foo, bar) 3 | -------------------------------------------------------------------------------- /testdata/select_from_left_join_on.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | left join quz on baz.a = quz.b 7 | -------------------------------------------------------------------------------- /testdata/select_from_left_join_on.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar 2 | from baz left join quz on baz.a = quz.b 3 | -------------------------------------------------------------------------------- /testdata/select_from_natural_join.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | natural join quz 7 | -------------------------------------------------------------------------------- /testdata/select_from_natural_join.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar 2 | from baz natural join quz 3 | -------------------------------------------------------------------------------- /testdata/select_into.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | into quz 5 | from 6 | baz 7 | -------------------------------------------------------------------------------- /testdata/select_into.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar into quz from baz 2 | -------------------------------------------------------------------------------- /testdata/select_star.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | * 3 | from 4 | baz 5 | -------------------------------------------------------------------------------- /testdata/select_star.input.sql: -------------------------------------------------------------------------------- 1 | select * from baz 2 | -------------------------------------------------------------------------------- /testdata/select_table_dot_column.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | baz.foo, 3 | baz.bar as quz 4 | from 5 | baz 6 | -------------------------------------------------------------------------------- /testdata/select_table_dot_column.input.sql: -------------------------------------------------------------------------------- 1 | select baz.foo, baz.bar as quz from baz 2 | -------------------------------------------------------------------------------- /testdata/select_table_dot_star.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | baz.* 3 | from 4 | baz 5 | -------------------------------------------------------------------------------- /testdata/select_table_dot_star.input.sql: -------------------------------------------------------------------------------- 1 | select baz.* from baz 2 | -------------------------------------------------------------------------------- /testdata/select_where.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | where 7 | foo > 5 8 | and bar < 2 9 | -------------------------------------------------------------------------------- /testdata/select_where.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz where foo > 5 and bar < 2 2 | -------------------------------------------------------------------------------- /testdata/select_wrapped_by_parens.golden.sql: -------------------------------------------------------------------------------- 1 | (select 2 | foo 3 | from 4 | bar 5 | ) 6 | -------------------------------------------------------------------------------- /testdata/select_wrapped_by_parens.input.sql: -------------------------------------------------------------------------------- 1 | (select foo from bar) 2 | -------------------------------------------------------------------------------- /testdata/semicolon.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo 3 | from 4 | bar 5 | ; 6 | -------------------------------------------------------------------------------- /testdata/semicolon.input.sql: -------------------------------------------------------------------------------- 1 | select foo from bar; 2 | -------------------------------------------------------------------------------- /testdata/simple_select_literal_integer.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | 42 3 | -------------------------------------------------------------------------------- /testdata/simple_select_literal_integer.input.sql: -------------------------------------------------------------------------------- 1 | select 42 2 | -------------------------------------------------------------------------------- /testdata/simple_select_literal_text.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | 'foo', 3 | 'bar' as quz, 4 | 'It''s' 5 | -------------------------------------------------------------------------------- /testdata/simple_select_literal_text.input.sql: -------------------------------------------------------------------------------- 1 | select 'foo', 'bar' as quz, 'It''s' 2 | -------------------------------------------------------------------------------- /testdata/simple_select_with_from.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | -------------------------------------------------------------------------------- /testdata/simple_select_with_from.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz 2 | -------------------------------------------------------------------------------- /testdata/simple_select_with_selection_alias.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo as f, 3 | bar as b 4 | from 5 | baz 6 | -------------------------------------------------------------------------------- /testdata/simple_select_with_selection_alias.input.sql: -------------------------------------------------------------------------------- 1 | select foo as f, bar as b 2 | from baz 3 | -------------------------------------------------------------------------------- /testdata/simple_select_with_selection_alias_no_as.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo as f, 3 | bar as b 4 | from 5 | baz 6 | -------------------------------------------------------------------------------- /testdata/simple_select_with_selection_alias_no_as.input.sql: -------------------------------------------------------------------------------- 1 | select foo f, bar b 2 | from baz 3 | -------------------------------------------------------------------------------- /testdata/simple_select_without_from.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | -------------------------------------------------------------------------------- /testdata/simple_select_without_from.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar 2 | -------------------------------------------------------------------------------- /testdata/subquery_op.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | 3 > any (select 3 | generate_series(1, 10) 4 | ) 5 | , 6 | 3 > all (select 7 | generate_series(1, 10) 8 | ) 9 | , 10 | 3 > any (array[1, 2, 3, 4]), 11 | 3 operator(>) any (array[1, 2, 3, 4]) 12 | -------------------------------------------------------------------------------- /testdata/subquery_op.input.sql: -------------------------------------------------------------------------------- 1 | select 3 > any (select generate_series(1,10)), 2 | 3 > all (select generate_series(1,10)), 3 | 3 > any (array[1,2,3,4]), 4 | 3 operator(>) any (array[1,2,3,4]) 5 | -------------------------------------------------------------------------------- /testdata/subselect_expression.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | (select 3 | 1 4 | from 5 | foo 6 | ) 7 | -------------------------------------------------------------------------------- /testdata/subselect_expression.input.sql: -------------------------------------------------------------------------------- 1 | select (select 1 from foo) 2 | -------------------------------------------------------------------------------- /testdata/substring.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | substring('Thomas' from 2 for 3), 3 | substring('Thomas' from '...$'), 4 | substring('Thomas' from '%#"o_a#"_' for '#'), 5 | substring('Thomas', 2, 3), 6 | substring() 7 | -------------------------------------------------------------------------------- /testdata/substring.input.sql: -------------------------------------------------------------------------------- 1 | select substring('Thomas' from 2 for 3), substring('Thomas' from '...$'), 2 | substring('Thomas' from '%#"o_a#"_' for '#'), substring('Thomas', 2, 3), 3 | substring() 4 | -------------------------------------------------------------------------------- /testdata/table.golden.sql: -------------------------------------------------------------------------------- 1 | table baz 2 | -------------------------------------------------------------------------------- /testdata/table.input.sql: -------------------------------------------------------------------------------- 1 | table baz 2 | -------------------------------------------------------------------------------- /testdata/table_only.golden.sql: -------------------------------------------------------------------------------- 1 | table only baz 2 | -------------------------------------------------------------------------------- /testdata/table_only.input.sql: -------------------------------------------------------------------------------- 1 | table only baz 2 | -------------------------------------------------------------------------------- /testdata/table_only_paren.golden.sql: -------------------------------------------------------------------------------- 1 | table only baz 2 | -------------------------------------------------------------------------------- /testdata/table_only_paren.input.sql: -------------------------------------------------------------------------------- 1 | table only (baz) 2 | -------------------------------------------------------------------------------- /testdata/table_qualified.golden.sql: -------------------------------------------------------------------------------- 1 | table foo.baz 2 | -------------------------------------------------------------------------------- /testdata/table_qualified.input.sql: -------------------------------------------------------------------------------- 1 | table foo.baz 2 | -------------------------------------------------------------------------------- /testdata/table_star.golden.sql: -------------------------------------------------------------------------------- 1 | table baz * 2 | -------------------------------------------------------------------------------- /testdata/table_star.input.sql: -------------------------------------------------------------------------------- 1 | table baz * 2 | -------------------------------------------------------------------------------- /testdata/treat.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | treat(42 as float8) 3 | -------------------------------------------------------------------------------- /testdata/treat.input.sql: -------------------------------------------------------------------------------- 1 | select treat(42 as float8) 2 | -------------------------------------------------------------------------------- /testdata/trim.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | trim(both 'x' from 'xBobxx'), 3 | trim(leading 'x' from 'xBobxx'), 4 | trim(trailing 'x' from 'xBobxx'), 5 | trim(both from 'xBobxx', 'x'), 6 | trim(leading from 'xBobxx', 'x'), 7 | trim(trailing from 'xBobxx', 'x'), 8 | trim(from 'xBobxx', 'x'), 9 | trim(from 'xBobxx'), 10 | trim('xBobxx', 'x'), 11 | trim('xBobxx') 12 | -------------------------------------------------------------------------------- /testdata/trim.input.sql: -------------------------------------------------------------------------------- 1 | select trim(both 'x' from 'xBobxx'), trim(leading 'x' from 'xBobxx'), trim(trailing 'x' from 'xBobxx'), 2 | trim(both from 'xBobxx', 'x'), trim(leading from 'xBobxx', 'x'), trim(trailing from 'xBobxx', 'x'), 3 | trim(from 'xBobxx', 'x'), trim(from 'xBobxx'), 4 | trim('xBobxx', 'x'), trim('xBobxx') 5 | -------------------------------------------------------------------------------- /testdata/typecast.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | '42'::integer, 3 | foo::text, 4 | (foo + bar)::text, 5 | '3.14'::numeric(8, 2), 6 | '123.1'::decimal(8, 1), 7 | '424.234'::dec(8, 3), 8 | '324.5'::float(20), 9 | '23.23'::double precision, 10 | 'asdf'::customtype(3), 11 | 'asdf'::myschema.customtype, 12 | '1942'::setof int, 13 | '{123,34}'::int array[4], 14 | '{123,34}'::setof int array[4], 15 | '{123,34}'::int array, 16 | '{123,34}'::setof int array, 17 | 'f'::char, 18 | 'fads'::varchar, 19 | 'fads'::char(10), 20 | 'fads'::varchar(10), 21 | 'f'::char, 22 | 'fads'::varchar, 23 | 'fads'::char(10), 24 | 'fads'::varchar(10), 25 | 'f'::char, 26 | 'fads'::varchar, 27 | 'f'::char, 28 | 'fads'::char(10), 29 | 'asdf'::varchar character set sql_text, 30 | '1'::bit, 31 | '1010'::bit(4), 32 | '1010'::varbit, 33 | '1010'::varbit, 34 | '00:30:00'::interval hour to minute, 35 | '00:15:00'::interval(2) 36 | from 37 | baz 38 | -------------------------------------------------------------------------------- /testdata/typecast.input.sql: -------------------------------------------------------------------------------- 1 | select '42'::integer, foo::text, (foo+bar)::text, '3.14'::numeric(8,2), 2 | '123.1'::decimal(8,1), '424.234'::dec(8,3), 3 | '324.5'::float(20), '23.23'::double precision, 4 | 'asdf'::customtype(3), 'asdf'::myschema.customtype, 5 | '1942'::setof int, 6 | '{123,34}'::int array[4], '{123,34}'::setof int array[4], 7 | '{123,34}'::int array, '{123,34}'::setof int array, 8 | 'f'::character, 'fads'::character varying, 9 | 'fads'::character(10), 'fads'::character varying(10), 10 | 'f'::char, 'fads'::char varying, 11 | 'fads'::char(10), 'fads'::char varying(10), 12 | 'f'::national character, 'fads'::national character varying, 13 | 'f'::nchar, 'fads'::nchar(10), 14 | 'asdf'::varchar character set sql_text, 15 | '1'::bit, '1010'::bit(4), '1010'::bit varying, '1010'::varbit, 16 | '00:30:00'::interval hour to minute, '00:15:00'::interval(2) 17 | 18 | from baz 19 | -------------------------------------------------------------------------------- /testdata/unary.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | +11, 3 | -42 4 | -------------------------------------------------------------------------------- /testdata/unary.input.sql: -------------------------------------------------------------------------------- 1 | select +11, -42 2 | -------------------------------------------------------------------------------- /testdata/union.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | bar 4 | from 5 | baz 6 | union all 7 | select 8 | a, 9 | b 10 | from 11 | quz 12 | -------------------------------------------------------------------------------- /testdata/union.input.sql: -------------------------------------------------------------------------------- 1 | select foo, bar from baz union all select a, b from quz 2 | -------------------------------------------------------------------------------- /testdata/values.golden.sql: -------------------------------------------------------------------------------- 1 | values 2 | (1, 2, 3), 3 | (4, 5, 6), 4 | (7, 8, 9) 5 | -------------------------------------------------------------------------------- /testdata/values.input.sql: -------------------------------------------------------------------------------- 1 | values(1,2,3), (4,5,6), (7,8,9) 2 | -------------------------------------------------------------------------------- /testdata/values_default.golden.sql: -------------------------------------------------------------------------------- 1 | values 2 | (1, default, 3), 3 | (4, 5, default), 4 | (default, 8, 9) 5 | -------------------------------------------------------------------------------- /testdata/values_default.input.sql: -------------------------------------------------------------------------------- 1 | values(1,default,3), (4,5, DEFAULT), (default,8,9) 2 | -------------------------------------------------------------------------------- /testdata/values_order.golden.sql: -------------------------------------------------------------------------------- 1 | values 2 | (1, 2, 3), 3 | (4, 5, 6), 4 | (7, 8, 9) 5 | order by 6 | 3 7 | -------------------------------------------------------------------------------- /testdata/values_order.input.sql: -------------------------------------------------------------------------------- 1 | values(1,2,3), (4,5,6), (7,8,9) order by 3 2 | -------------------------------------------------------------------------------- /testdata/window_function.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | row_number() over () 4 | from 5 | baz 6 | -------------------------------------------------------------------------------- /testdata/window_function.input.sql: -------------------------------------------------------------------------------- 1 | select foo, row_number() over () from baz 2 | -------------------------------------------------------------------------------- /testdata/window_function_frame.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | row_number() over (range unbounded preceding), 4 | row_number() over (rows unbounded preceding), 5 | row_number() over (range between unbounded preceding and 3 following), 6 | row_number() over (rows between unbounded preceding and 3 following), 7 | row_number() over (range current row), 8 | row_number() over (rows current row), 9 | row_number() over (range between 2 preceding and unbounded following), 10 | row_number() over (rows between 2 preceding and unbounded following) 11 | from 12 | baz 13 | -------------------------------------------------------------------------------- /testdata/window_function_frame.input.sql: -------------------------------------------------------------------------------- 1 | select foo, 2 | row_number() over (range unbounded preceding), 3 | row_number() over (rows unbounded preceding), 4 | row_number() over (range between unbounded preceding and 3 following), 5 | row_number() over (rows between unbounded preceding and 3 following), 6 | row_number() over (range current row), 7 | row_number() over (rows current row), 8 | row_number() over (range between 2 preceding and unbounded following), 9 | row_number() over (rows between 2 preceding and unbounded following) 10 | from baz 11 | -------------------------------------------------------------------------------- /testdata/window_function_named.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | row_number() over w 4 | from 5 | baz 6 | window 7 | w as (partition by quz order by abc) 8 | -------------------------------------------------------------------------------- /testdata/window_function_named.input.sql: -------------------------------------------------------------------------------- 1 | select foo, row_number() over w from baz window w as (partition by quz order by abc) 2 | -------------------------------------------------------------------------------- /testdata/window_function_named_multiple.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | row_number() over w 4 | from 5 | baz 6 | window 7 | w as (partition by quz), 8 | w2 as (w order by abc) 9 | -------------------------------------------------------------------------------- /testdata/window_function_named_multiple.input.sql: -------------------------------------------------------------------------------- 1 | select foo, row_number() over w from baz window w as (partition by quz), w2 as (w order by abc) 2 | -------------------------------------------------------------------------------- /testdata/window_function_order_by.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | row_number() over (order by quz) 4 | from 5 | baz 6 | -------------------------------------------------------------------------------- /testdata/window_function_order_by.input.sql: -------------------------------------------------------------------------------- 1 | select foo, row_number() over (order by quz) from baz 2 | -------------------------------------------------------------------------------- /testdata/window_function_partition_by.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | foo, 3 | row_number() over (partition by quz) 4 | from 5 | baz 6 | -------------------------------------------------------------------------------- /testdata/window_function_partition_by.input.sql: -------------------------------------------------------------------------------- 1 | select foo, row_number() over (partition by quz) from baz 2 | -------------------------------------------------------------------------------- /testdata/xmlelement.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | xmlelement(name foo), 3 | xmlelement(name foo, xmlattributes('bar' as baz)), 4 | xmlelement(name foo, xmlattributes(bar, baz)), 5 | xmlelement(name foo, xmlattributes('bar' as baz), 'bo', 'dy'), 6 | xmlelement(name foo, 'bo', 'dy') 7 | -------------------------------------------------------------------------------- /testdata/xmlelement.input.sql: -------------------------------------------------------------------------------- 1 | select xmlelement(name foo), xmlelement(name foo, xmlattributes('bar' as baz)), 2 | xmlelement(name foo, xmlattributes(bar, baz)), 3 | xmlelement(name foo, xmlattributes('bar' as baz), 'bo', 'dy'), 4 | xmlelement(name foo, 'bo', 'dy') 5 | -------------------------------------------------------------------------------- /testdata/xmlexists.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | xmlexists('//town[text() = ''Toronto'']' passing 'TorontoOttawa'), 3 | xmlexists('//town[text() = ''Toronto'']' passing by ref 'TorontoOttawa' by ref) 4 | -------------------------------------------------------------------------------- /testdata/xmlexists.input.sql: -------------------------------------------------------------------------------- 1 | select xmlexists('//town[text() = ''Toronto'']' passing 'TorontoOttawa'), 2 | xmlexists('//town[text() = ''Toronto'']' passing by ref 'TorontoOttawa' by ref) 3 | -------------------------------------------------------------------------------- /testdata/xmlforest.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | xmlforest('abc' as foo, 'xyz' as bar, baz) 3 | -------------------------------------------------------------------------------- /testdata/xmlforest.input.sql: -------------------------------------------------------------------------------- 1 | select xmlforest('abc' as foo, 'xyz' as bar, baz) 2 | -------------------------------------------------------------------------------- /testdata/xmlparse.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | xmlparse(document 'John'), 3 | xmlparse(content 'John'), 4 | xmlparse(content 'John' preserve whitespace), 5 | xmlparse(content 'John' strip whitespace) 6 | -------------------------------------------------------------------------------- /testdata/xmlparse.input.sql: -------------------------------------------------------------------------------- 1 | select xmlparse(document 'John'), 2 | xmlparse(content 'John'), 3 | xmlparse(content 'John' preserve whitespace), 4 | xmlparse(content 'John' strip whitespace) 5 | -------------------------------------------------------------------------------- /testdata/xmlpi.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | xmlpi(name foo), 3 | xmlpi(name foo, 'bar') 4 | -------------------------------------------------------------------------------- /testdata/xmlpi.input.sql: -------------------------------------------------------------------------------- 1 | select xmlpi(name foo), xmlpi(name foo, 'bar') 2 | -------------------------------------------------------------------------------- /testdata/xmlroot.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | xmlroot(xmlparse(document 'abc'), version '1.0', standalone yes), 3 | xmlroot(xmlparse(document 'abc'), version '1.0', standalone no), 4 | xmlroot(xmlparse(document 'abc'), version '1.0', standalone no value), 5 | xmlroot(xmlparse(document 'abc'), version '1.0'), 6 | xmlroot(xmlparse(document 'abc'), version no value) 7 | -------------------------------------------------------------------------------- /testdata/xmlroot.input.sql: -------------------------------------------------------------------------------- 1 | select xmlroot(xmlparse(document 'abc'), version '1.0', standalone yes), 2 | xmlroot(xmlparse(document 'abc'), version '1.0', standalone no), 3 | xmlroot(xmlparse(document 'abc'), version '1.0', standalone no value), 4 | xmlroot(xmlparse(document 'abc'), version '1.0'), 5 | xmlroot(xmlparse(document 'abc'), version no value) 6 | -------------------------------------------------------------------------------- /testdata/xmlserialize.golden.sql: -------------------------------------------------------------------------------- 1 | select 2 | xmlserialize(content 'bar' as text), 3 | xmlserialize(document 'bar' as text) 4 | -------------------------------------------------------------------------------- /testdata/xmlserialize.input.sql: -------------------------------------------------------------------------------- 1 | select xmlserialize(content 'bar' as text), xmlserialize(document 'bar' as text) 2 | -------------------------------------------------------------------------------- /token_renderer.go: -------------------------------------------------------------------------------- 1 | package sqlfmt 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | type TokenRenderer []RenderToken 8 | 9 | func (r *TokenRenderer) Text(val string, tokenType int) { 10 | *r = TokenRenderer(append([]RenderToken(*r), RenderToken{Type: tokenType, Value: val})) 11 | } 12 | 13 | func (r *TokenRenderer) Control(tokenType int) { 14 | *r = TokenRenderer(append([]RenderToken(*r), RenderToken{Type: tokenType})) 15 | } 16 | 17 | func RenderTokens(r Renderer, tokens []RenderToken) { 18 | for _, t := range tokens { 19 | if t.Value != "" { 20 | r.Text(t.Value, t.Type) 21 | } else { 22 | r.Control(t.Type) 23 | } 24 | } 25 | } 26 | 27 | func TryOneLine(tokens []RenderToken, maxLineLength int) []RenderToken { 28 | buf := &bytes.Buffer{} 29 | r := NewTextRenderer(buf) 30 | RenderTokens(r, tokens) 31 | if buf.Len() < maxLineLength { 32 | filteredTokens := []RenderToken{} 33 | for _, t := range tokens { 34 | switch t.Type { 35 | case NewLineToken, IndentToken, UnindentToken: 36 | // filter these out 37 | default: 38 | filteredTokens = append(filteredTokens, t) 39 | } 40 | } 41 | 42 | tokens = filteredTokens 43 | } 44 | 45 | return tokens 46 | } 47 | -------------------------------------------------------------------------------- /token_renderer_test.go: -------------------------------------------------------------------------------- 1 | package sqlfmt 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestTokenRenderer(t *testing.T) { 9 | r := TokenRenderer(nil) 10 | r.Text("select", KeywordToken) 11 | r.Control(NewLineToken) 12 | r.Control(IndentToken) 13 | r.Text("foo", IdentifierToken) 14 | r.Control(NewLineToken) 15 | r.Control(UnindentToken) 16 | r.Text("from", KeywordToken) 17 | r.Control(NewLineToken) 18 | r.Control(IndentToken) 19 | r.Text("bar", IdentifierToken) 20 | 21 | expected := []RenderToken{ 22 | {Type: KeywordToken, Value: "select"}, 23 | {Type: NewLineToken}, 24 | {Type: IndentToken}, 25 | {Type: IdentifierToken, Value: "foo"}, 26 | {Type: NewLineToken}, 27 | {Type: UnindentToken}, 28 | {Type: KeywordToken, Value: "from"}, 29 | {Type: NewLineToken}, 30 | {Type: IndentToken}, 31 | {Type: IdentifierToken, Value: "bar"}, 32 | } 33 | 34 | if !reflect.DeepEqual([]RenderToken(r), expected) { 35 | t.Errorf("Expected `%v`, got `%v`", expected, []RenderToken(r)) 36 | } 37 | } 38 | --------------------------------------------------------------------------------