update git ignore, remove jet
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -54,6 +54,5 @@ metagen_config.json
|
||||
*.sqlite
|
||||
*.db
|
||||
|
||||
# jet generator
|
||||
tools/jet-2.12.0/jet
|
||||
tools/jet-2.12.0/jet.exe
|
||||
# docker
|
||||
docker-compose.yml
|
||||
@@ -1,24 +0,0 @@
|
||||
version: '3'
|
||||
services:
|
||||
http:
|
||||
build: .
|
||||
container_name: maxwarden
|
||||
ports:
|
||||
- 9090:80
|
||||
environment:
|
||||
- DOMAIN=https://example.net
|
||||
- HOST=0.0.0.0
|
||||
- PORT=80
|
||||
- IDENTITY_PRIVATE_KEY=8/!3zquteT\ec4!0N00/6~tU[^ydX3Q3FMHkQWS4pN4zWXPip>nAH`Df*kz[>"znzbuCWQU1c"hjwTazlC63(uoBS"H47_DJF/Bi,EE5v79FgHbFuzl35ci6UPlGr1pe
|
||||
- IDENTITY_DEFAULT_PASSWORD=test
|
||||
- SESSION_PRIVATE_KEY=lol
|
||||
- SMTP_SERVER=
|
||||
- SMTP_PORT=
|
||||
- SMTP_USERNAME=
|
||||
- SMTP_DISPLAY_FROM=
|
||||
- SMTP_PASSWORD=
|
||||
- SMTP_REQUIRE_AUTH=
|
||||
volumes:
|
||||
- ./passwords.db:/app/passwords.db:Z
|
||||
restart: always
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
# Golang CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
||||
version: 2.1
|
||||
orbs:
|
||||
codecov: codecov/codecov@3.1.1
|
||||
jobs:
|
||||
build_and_tests:
|
||||
docker:
|
||||
# specify the version
|
||||
- image: cimg/go:1.22.8
|
||||
- image: cimg/postgres:14.10
|
||||
environment:
|
||||
POSTGRES_USER: jet
|
||||
POSTGRES_PASSWORD: jet
|
||||
POSTGRES_DB: jetdb
|
||||
PGPORT: 50901
|
||||
|
||||
- image: circleci/mysql:8.0.27
|
||||
command: [ --default-authentication-plugin=mysql_native_password ]
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: jet
|
||||
MYSQL_DATABASE: dvds
|
||||
MYSQL_USER: jet
|
||||
MYSQL_PASSWORD: jet
|
||||
MYSQL_TCP_PORT: 50902
|
||||
|
||||
- image: circleci/mariadb:10.3
|
||||
command: [ '--default-authentication-plugin=mysql_native_password', '--port=50903' ]
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: jet
|
||||
MYSQL_DATABASE: dvds
|
||||
MYSQL_USER: jet
|
||||
MYSQL_PASSWORD: jet
|
||||
|
||||
- image: cockroachdb/cockroach-unstable:v23.1.0-rc.2
|
||||
command: ['start-single-node', '--accept-sql-without-tls']
|
||||
environment:
|
||||
COCKROACH_USER: jet
|
||||
COCKROACH_PASSWORD: jet
|
||||
COCKROACH_DATABASE: jetdb
|
||||
|
||||
environment: # environment variables for the build itself
|
||||
TEST_RESULTS: /tmp/test-results # path to where test results will be saved
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Submodule init
|
||||
command: cd tests && make checkout-testdata
|
||||
|
||||
- restore_cache: # restores saved cache if no changes are detected since last run
|
||||
keys:
|
||||
- go-mod-v4-{{ checksum "go.sum" }}
|
||||
|
||||
- run:
|
||||
name: Install jet generator
|
||||
command: cd tests && make install-jet-gen
|
||||
|
||||
- run:
|
||||
name: Waiting for Postgres to be ready
|
||||
command: |
|
||||
for i in `seq 1 10`;
|
||||
do
|
||||
nc -z localhost 50901 && echo Success && exit 0
|
||||
echo -n .
|
||||
sleep 1
|
||||
done
|
||||
echo Failed waiting for Postgres && exit 1
|
||||
|
||||
- run:
|
||||
name: Waiting for MySQL to be ready
|
||||
command: |
|
||||
for i in `seq 1 10`;
|
||||
do
|
||||
nc -z 127.0.0.1 50902 && echo Success && exit 0
|
||||
echo -n .
|
||||
sleep 1
|
||||
done
|
||||
echo Failed waiting for MySQL && exit 1
|
||||
|
||||
- run:
|
||||
name: Waiting for MariaDB to be ready
|
||||
command: |
|
||||
for i in `seq 1 10`;
|
||||
do
|
||||
nc -z 127.0.0.1 50903 && echo Success && exit 0
|
||||
echo -n .
|
||||
sleep 1
|
||||
done
|
||||
echo Failed waiting for MySQL && exit 1
|
||||
|
||||
- run:
|
||||
name: Waiting for Cockroach to be ready
|
||||
command: |
|
||||
for i in `seq 1 10`;
|
||||
do
|
||||
nc -z localhost 26257 && echo Success && exit 0
|
||||
echo -n .
|
||||
sleep 1
|
||||
done
|
||||
echo Failed waiting for Cockroach && exit 1
|
||||
|
||||
- run:
|
||||
name: Install MySQL CLI;
|
||||
command: |
|
||||
sudo apt-get --allow-releaseinfo-change update && sudo apt-get install default-mysql-client
|
||||
|
||||
- run:
|
||||
name: Create MySQL/MariaDB user and test databases
|
||||
command: |
|
||||
mysql -h 127.0.0.1 -P 50902 -u root -pjet -e "grant all privileges on *.* to 'jet'@'%';"
|
||||
mysql -h 127.0.0.1 -P 50902 -u root -pjet -e "set global sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';"
|
||||
mysql -h 127.0.0.1 -P 50902 -u jet -pjet -e "create database test_sample"
|
||||
mysql -h 127.0.0.1 -P 50902 -u jet -pjet -e "create database dvds2"
|
||||
|
||||
mysql -h 127.0.0.1 -P 50903 -u root -pjet -e "grant all privileges on *.* to 'jet'@'%';"
|
||||
mysql -h 127.0.0.1 -P 50903 -u root -pjet -e "set global sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';"
|
||||
mysql -h 127.0.0.1 -P 50903 -u jet -pjet -e "create database test_sample"
|
||||
mysql -h 127.0.0.1 -P 50903 -u jet -pjet -e "create database dvds2"
|
||||
|
||||
- run:
|
||||
name: Init databases
|
||||
command: |
|
||||
cd tests
|
||||
go run ./init/init.go -testsuite all
|
||||
|
||||
- run:
|
||||
name: Install gotestsum
|
||||
command: go install gotest.tools/gotestsum@latest
|
||||
|
||||
# to create test results report
|
||||
- run: mkdir -p $TEST_RESULTS
|
||||
|
||||
- run:
|
||||
name: Running tests
|
||||
command: gotestsum --junitfile $TEST_RESULTS/report.xml --format testname -- -coverprofile=cover.out -covermode=atomic -coverpkg=github.com/go-jet/jet/v2/postgres/...,github.com/go-jet/jet/v2/mysql/...,github.com/go-jet/jet/v2/sqlite/...,github.com/go-jet/jet/v2/qrm/...,github.com/go-jet/jet/v2/generator/...,github.com/go-jet/jet/v2/internal/...,github.com/go-jet/jet/v2/stmtcache/... ./...
|
||||
|
||||
- run:
|
||||
name: Running tests with statement caching enabled
|
||||
command: JET_TESTS_WITH_STMT_CACHE=true go test -v ./tests/...
|
||||
|
||||
# run mariaDB and cockroachdb tests. No need to collect coverage, because coverage is already included with mysql and postgres tests
|
||||
- run: MY_SQL_SOURCE=MariaDB go test -v ./tests/mysql/
|
||||
- run: PG_SOURCE=COCKROACH_DB go test -v ./tests/postgres/
|
||||
|
||||
- save_cache:
|
||||
key: go-mod-v4-{{ checksum "go.sum" }}
|
||||
paths:
|
||||
- "/go/pkg/mod"
|
||||
|
||||
- codecov/upload:
|
||||
file: cover.out
|
||||
|
||||
- store_artifacts: # Upload test summary for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
|
||||
path: /tmp/test-results
|
||||
destination: raw-test-output
|
||||
|
||||
- store_test_results: # Upload test results for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
|
||||
path: /tmp/test-results
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_test:
|
||||
jobs:
|
||||
- build_and_tests
|
||||
3
tools/jet-2.12.0/.gitattributes
vendored
3
tools/jet-2.12.0/.gitattributes
vendored
@@ -1,3 +0,0 @@
|
||||
|
||||
*.sql linguist-detectable=false
|
||||
*.json linguist-detectable=false
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- OS: [e.g. linux, windows, macosx]
|
||||
- Database: [e.g. postgres, mysql, sqlite]
|
||||
- Database driver: [e.g. pq, pgx]
|
||||
- Jet version [e.g. 2.6.0 or branch name]
|
||||
|
||||
**Code snippet**
|
||||
Query statement and model files of interest.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: missing feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
6
tools/jet-2.12.0/.github/dependabot.yml
vendored
6
tools/jet-2.12.0/.github/dependabot.yml
vendored
@@ -1,6 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
@@ -1,45 +0,0 @@
|
||||
name: Code Scanners
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
go_version: "1.22.8"
|
||||
|
||||
|
||||
jobs:
|
||||
security_scanning:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.go_version }}
|
||||
cache: true
|
||||
- name: Setup Tools
|
||||
run: |
|
||||
go install github.com/securego/gosec/v2/cmd/gosec@latest
|
||||
- name: Running Scan
|
||||
run: gosec --exclude=G402,G304 ./...
|
||||
lint_scanner:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.go_version }}
|
||||
cache: true
|
||||
- name: Setup Tools
|
||||
run: |
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
- name: Running Scan
|
||||
run: golangci-lint run --timeout=30m ./...
|
||||
@@ -1,70 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '36 17 * * 0'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
26
tools/jet-2.12.0/.gitignore
vendored
26
tools/jet-2.12.0/.gitignore
vendored
@@ -1,26 +0,0 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Idea
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# Test files
|
||||
gen
|
||||
.gentestdata
|
||||
.tests/testdata/
|
||||
.gen
|
||||
.docker
|
||||
.env
|
||||
.tempTestDir
|
||||
.gentestdata3
|
||||
3
tools/jet-2.12.0/.gitmodules
vendored
3
tools/jet-2.12.0/.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "tests/testdata"]
|
||||
path = tests/testdata
|
||||
url = https://github.com/go-jet/jet-test-data
|
||||
@@ -1,19 +0,0 @@
|
||||
run:
|
||||
# The default concurrency value is the number of available CPU.
|
||||
concurrency: 4
|
||||
# Timeout for analysis, e.g. 30s, 5m.
|
||||
# Default: 1m
|
||||
timeout: 30m
|
||||
# Exit code when at least one issue was found.
|
||||
# Default: 1
|
||||
issues-exit-code: 2
|
||||
# Include test files or not.
|
||||
# Default: true
|
||||
tests: false
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- tests
|
||||
exclude-files:
|
||||
- "_test.go"
|
||||
- "testutils.go"
|
||||
@@ -1,262 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2019 Goran Bjelanovic
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
------------------------------------------------------------------------------------
|
||||
|
||||
This product builds on various third-party components under other open source licenses.
|
||||
This section summarizes those components and their licenses.
|
||||
|
||||
|
||||
https://github.com/dropbox/godropbox
|
||||
---------------------------------------
|
||||
|
||||
Copyright (c) 2014 Dropbox, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
https://github.com/serenize/snaker
|
||||
---------------------------------------
|
||||
|
||||
Copyright (c) 2015 Serenize UG (haftungsbeschränkt)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,14 +0,0 @@
|
||||
Jet
|
||||
Copyright 2019 Goran Bjelanovic
|
||||
|
||||
|
||||
This product contains a modified portion of 'godropbox' which can be obtained at:
|
||||
https://github.com/dropbox/godropbox/tree/master/database/sqlbuilder (BSD-3)
|
||||
|
||||
|
||||
This product contains a modified portion of 'snaker' which can be obtained at:
|
||||
https://github.com/serenize/snaker (MIT)
|
||||
|
||||
|
||||
This product contains `FormatTimestamp` function from 'pq' which can be obtained at:
|
||||
https://github.com/lib/pq (MIT)
|
||||
@@ -1,583 +0,0 @@
|
||||
# Jet
|
||||
|
||||
[](https://app.circleci.com/pipelines/github/go-jet/jet?branch=master)
|
||||
[](https://codecov.io/gh/go-jet/jet)
|
||||
[](https://goreportcard.com/report/github.com/go-jet/jet/v2)
|
||||
[](http://godoc.org/github.com/go-jet/jet/v2)
|
||||
[](https://github.com/go-jet/jet/releases)
|
||||
|
||||
Jet is a complete solution for efficient and high performance database access, consisting of type-safe SQL builder
|
||||
with code generation and automatic query result data mapping.
|
||||
Jet currently supports `PostgreSQL`, `MySQL`, `CockroachDB`, `MariaDB` and `SQLite`. Future releases will add support for additional databases.
|
||||
|
||||

|
||||
Jet is the easiest, and the fastest way to write complex type-safe SQL queries as a Go code and map database query result
|
||||
into complex object composition. __It is not an ORM.__
|
||||
|
||||
## Motivation
|
||||
https://medium.com/@go.jet/jet-5f3667efa0cc
|
||||
|
||||
## Contents
|
||||
- [Features](#features)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Generate sql builder and model types](#generate-sql-builder-and-model-types)
|
||||
- [Lets write some SQL queries in Go](#lets-write-some-sql-queries-in-go)
|
||||
- [Execute query and store result](#execute-query-and-store-result)
|
||||
- [Benefits](#benefits)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Versioning](#versioning)
|
||||
- [License](#license)
|
||||
|
||||
## Features
|
||||
1) Auto-generated type-safe SQL Builder. Statements supported:
|
||||
* [SELECT](https://github.com/go-jet/jet/wiki/SELECT) `(DISTINCT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET, FOR, LOCK_IN_SHARE_MODE, UNION, INTERSECT, EXCEPT, WINDOW, sub-queries)`
|
||||
* [INSERT](https://github.com/go-jet/jet/wiki/INSERT) `(VALUES, MODEL, MODELS, QUERY, ON_CONFLICT/ON_DUPLICATE_KEY_UPDATE, RETURNING)`,
|
||||
* [UPDATE](https://github.com/go-jet/jet/wiki/UPDATE) `(SET, MODEL, WHERE, RETURNING)`,
|
||||
* [DELETE](https://github.com/go-jet/jet/wiki/DELETE) `(WHERE, ORDER_BY, LIMIT, RETURNING)`,
|
||||
* [LOCK](https://github.com/go-jet/jet/wiki/LOCK) `(IN, NOWAIT)`, `(READ, WRITE)`
|
||||
* [WITH](https://github.com/go-jet/jet/wiki/WITH)
|
||||
|
||||
2) Auto-generated Data Model types - Go types mapped to database type (table, view or enum), used to store
|
||||
result of database queries. Can be combined to create complex query result destination.
|
||||
3) Query execution with result mapping to arbitrary destination.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To install Jet package, you need to install Go and set your Go workspace first.
|
||||
|
||||
[Go](https://golang.org/) **version 1.18+ is required**
|
||||
|
||||
### Installation
|
||||
|
||||
Use the command bellow to add jet as a dependency into `go.mod` project:
|
||||
```sh
|
||||
$ go get -u github.com/go-jet/jet/v2
|
||||
```
|
||||
|
||||
Jet generator can be installed in one of the following ways:
|
||||
|
||||
- (Go1.16+) Install jet generator using go install:
|
||||
```sh
|
||||
go install github.com/go-jet/jet/v2/cmd/jet@latest
|
||||
```
|
||||
*Jet generator is installed to the directory named by the GOBIN environment variable,
|
||||
which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH environment variable is not set.*
|
||||
|
||||
- Install jet generator to specific folder:
|
||||
```sh
|
||||
git clone https://github.com/go-jet/jet.git
|
||||
cd jet && go build -o dir_path ./cmd/jet
|
||||
```
|
||||
*Make sure `dir_path` folder is added to the PATH environment variable.*
|
||||
|
||||
|
||||
|
||||
### Quick Start
|
||||
For this quick start example we will use PostgreSQL sample _'dvd rental'_ database. Full database dump can be found in
|
||||
[./tests/testdata/init/postgres/dvds.sql](https://github.com/go-jet/jet-test-data/blob/master/init/postgres/dvds.sql).
|
||||
Schema diagram of interest can be found [here](./examples/quick-start/diagram.png).
|
||||
|
||||
#### Generate SQL Builder and Model types
|
||||
To generate jet SQL Builder and Data Model types from running postgres database, we need to call `jet` generator with postgres
|
||||
connection parameters and destination folder path.
|
||||
Assuming we are running local postgres database, with user `user`, user password `pass`, database `jetdb` and
|
||||
schema `dvds` we will use this command:
|
||||
```sh
|
||||
jet -dsn=postgresql://user:pass@localhost:5432/jetdb?sslmode=disable -schema=dvds -path=./.gen
|
||||
```
|
||||
```sh
|
||||
Connecting to postgres database: postgresql://user:pass@localhost:5432/jetdb?sslmode=disable
|
||||
Retrieving schema information...
|
||||
FOUND 15 table(s), 7 view(s), 1 enum(s)
|
||||
Cleaning up destination directory...
|
||||
Generating table sql builder files...
|
||||
Generating view sql builder files...
|
||||
Generating enum sql builder files...
|
||||
Generating table model files...
|
||||
Generating view model files...
|
||||
Generating enum model files...
|
||||
Done
|
||||
```
|
||||
Procedure is similar for MySQL, CockroachDB, MariaDB and SQLite. For example:
|
||||
```sh
|
||||
jet -source=mysql -dsn="user:pass@tcp(localhost:3306)/dbname" -path=./.gen
|
||||
jet -dsn=postgres://user:pass@localhost:26257/jetdb?sslmode=disable -schema=dvds -path=./.gen #cockroachdb
|
||||
jet -dsn="mariadb://user:pass@tcp(localhost:3306)/dvds" -path=./.gen # source flag can be omitted if data source appears in dsn
|
||||
jet -source=sqlite -dsn="/path/to/sqlite/database/file" -schema=dvds -path=./.gen
|
||||
jet -dsn="file:///path/to/sqlite/database/file" -schema=dvds -path=./.gen # sqlite database assumed for 'file' data sources
|
||||
```
|
||||
_*User has to have a permission to read information schema tables._
|
||||
|
||||
As command output suggest, Jet will:
|
||||
- connect to postgres database and retrieve information about the _tables_, _views_ and _enums_ of `dvds` schema
|
||||
- delete everything in schema destination folder - `./.gen/jetdb/dvds`,
|
||||
- and finally generate SQL Builder and Model types for each schema table, view and enum.
|
||||
|
||||
|
||||
Generated files folder structure will look like this:
|
||||
```sh
|
||||
|-- .gen # path
|
||||
| -- jetdb # database name
|
||||
| -- dvds # schema name
|
||||
| |-- enum # sql builder package for enums
|
||||
| | |-- mpaa_rating.go
|
||||
| |-- table # sql builder package for tables
|
||||
| |-- actor.go
|
||||
| |-- address.go
|
||||
| |-- category.go
|
||||
| ...
|
||||
| |-- view # sql builder package for views
|
||||
| |-- actor_info.go
|
||||
| |-- film_list.go
|
||||
| ...
|
||||
| |-- model # data model types for each table, view and enum
|
||||
| | |-- actor.go
|
||||
| | |-- address.go
|
||||
| | |-- mpaa_rating.go
|
||||
| | ...
|
||||
```
|
||||
Types from `table`, `view` and `enum` are used to write type safe SQL in Go, and `model` types are combined to store
|
||||
results of the SQL queries.
|
||||
|
||||
|
||||
|
||||
#### Let's write some SQL queries in Go
|
||||
|
||||
First we need to import postgres SQLBuilder and generated packages from the previous step:
|
||||
```go
|
||||
import (
|
||||
// dot import so go code would resemble as much as native SQL
|
||||
// dot import is not mandatory
|
||||
. "github.com/go-jet/jet/v2/examples/quick-start/.gen/jetdb/dvds/table"
|
||||
. "github.com/go-jet/jet/v2/postgres"
|
||||
|
||||
"github.com/go-jet/jet/v2/examples/quick-start/.gen/jetdb/dvds/model"
|
||||
)
|
||||
```
|
||||
Let's say we want to retrieve the list of all _actors_ that acted in _films_ longer than 180 minutes, _film language_ is 'English'
|
||||
and _film category_ is not 'Action'.
|
||||
```golang
|
||||
stmt := SELECT(
|
||||
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate, // or just Actor.AllColumns
|
||||
Film.AllColumns,
|
||||
Language.AllColumns.Except(Language.LastUpdate), // all language columns except last_update
|
||||
Category.AllColumns,
|
||||
).FROM(
|
||||
Actor.
|
||||
INNER_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.ActorID)).
|
||||
INNER_JOIN(Film, Film.FilmID.EQ(FilmActor.FilmID)).
|
||||
INNER_JOIN(Language, Language.LanguageID.EQ(Film.LanguageID)).
|
||||
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
|
||||
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
|
||||
).WHERE(
|
||||
Language.Name.EQ(Char(20)("English")).
|
||||
AND(Category.Name.NOT_EQ(Text("Action"))).
|
||||
AND(Film.Length.GT(Int32(180))),
|
||||
).ORDER_BY(
|
||||
Actor.ActorID.ASC(),
|
||||
Film.FilmID.ASC(),
|
||||
)
|
||||
```
|
||||
_Package(dot) import is used, so the statements look as close as possible to the native SQL._
|
||||
Note that every column has a type. String column `Language.Name` and `Category.Name` can be compared only with
|
||||
string columns and expressions. `Actor.ActorID`, `FilmActor.ActorID`, `Film.Length` are integer columns
|
||||
and can be compared only with integer columns and expressions.
|
||||
|
||||
__How to get a parametrized SQL query from the statement?__
|
||||
```go
|
||||
query, args := stmt.Sql()
|
||||
```
|
||||
query - parametrized query
|
||||
args - query parameters
|
||||
|
||||
<details>
|
||||
<summary>Click to see `query` and `args`</summary>
|
||||
|
||||
```sql
|
||||
SELECT actor.actor_id AS "actor.actor_id",
|
||||
actor.first_name AS "actor.first_name",
|
||||
actor.last_name AS "actor.last_name",
|
||||
actor.last_update AS "actor.last_update",
|
||||
film.film_id AS "film.film_id",
|
||||
film.title AS "film.title",
|
||||
film.description AS "film.description",
|
||||
film.release_year AS "film.release_year",
|
||||
film.language_id AS "film.language_id",
|
||||
film.rental_duration AS "film.rental_duration",
|
||||
film.rental_rate AS "film.rental_rate",
|
||||
film.length AS "film.length",
|
||||
film.replacement_cost AS "film.replacement_cost",
|
||||
film.rating AS "film.rating",
|
||||
film.last_update AS "film.last_update",
|
||||
film.special_features AS "film.special_features",
|
||||
film.fulltext AS "film.fulltext",
|
||||
language.language_id AS "language.language_id",
|
||||
language.name AS "language.name",
|
||||
language.last_update AS "language.last_update",
|
||||
category.category_id AS "category.category_id",
|
||||
category.name AS "category.name",
|
||||
category.last_update AS "category.last_update"
|
||||
FROM dvds.actor
|
||||
INNER JOIN dvds.film_actor ON (actor.actor_id = film_actor.actor_id)
|
||||
INNER JOIN dvds.film ON (film.film_id = film_actor.film_id)
|
||||
INNER JOIN dvds.language ON (language.language_id = film.language_id)
|
||||
INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id)
|
||||
INNER JOIN dvds.category ON (category.category_id = film_category.category_id)
|
||||
WHERE ((language.name = $1::char(20)) AND (category.name != $2::text)) AND (film.length > $3::integer)
|
||||
ORDER BY actor.actor_id ASC, film.film_id ASC;
|
||||
```
|
||||
```sh
|
||||
[English Action 180]
|
||||
```
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
__How to get debug SQL from statement?__
|
||||
```go
|
||||
debugSql := stmt.DebugSql()
|
||||
```
|
||||
debugSql - this query string can be copy-pasted to sql editor and executed. __It is not intended to be used in production. For debug purposes only!!!__
|
||||
|
||||
<details>
|
||||
<summary>Click to see debug sql</summary>
|
||||
|
||||
```sql
|
||||
SELECT actor.actor_id AS "actor.actor_id",
|
||||
actor.first_name AS "actor.first_name",
|
||||
actor.last_name AS "actor.last_name",
|
||||
actor.last_update AS "actor.last_update",
|
||||
film.film_id AS "film.film_id",
|
||||
film.title AS "film.title",
|
||||
film.description AS "film.description",
|
||||
film.release_year AS "film.release_year",
|
||||
film.language_id AS "film.language_id",
|
||||
film.rental_duration AS "film.rental_duration",
|
||||
film.rental_rate AS "film.rental_rate",
|
||||
film.length AS "film.length",
|
||||
film.replacement_cost AS "film.replacement_cost",
|
||||
film.rating AS "film.rating",
|
||||
film.last_update AS "film.last_update",
|
||||
film.special_features AS "film.special_features",
|
||||
film.fulltext AS "film.fulltext",
|
||||
language.language_id AS "language.language_id",
|
||||
language.name AS "language.name",
|
||||
language.last_update AS "language.last_update",
|
||||
category.category_id AS "category.category_id",
|
||||
category.name AS "category.name",
|
||||
category.last_update AS "category.last_update"
|
||||
FROM dvds.actor
|
||||
INNER JOIN dvds.film_actor ON (actor.actor_id = film_actor.actor_id)
|
||||
INNER JOIN dvds.film ON (film.film_id = film_actor.film_id)
|
||||
INNER JOIN dvds.language ON (language.language_id = film.language_id)
|
||||
INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id)
|
||||
INNER JOIN dvds.category ON (category.category_id = film_category.category_id)
|
||||
WHERE ((language.name = 'English'::char(20)) AND (category.name != 'Action'::text)) AND (film.length > 180::integer)
|
||||
ORDER BY actor.actor_id ASC, film.film_id ASC;
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
#### Execute query and store result
|
||||
|
||||
Well-formed SQL is just a first half of the job. Let's see how can we make some sense of result set returned executing
|
||||
above statement. Usually this is the most complex and tedious work, but with Jet it is the easiest.
|
||||
|
||||
First we have to create desired structure to store query result.
|
||||
This is done be combining autogenerated model types, or it can be done
|
||||
by combining custom model types(see [wiki](https://github.com/go-jet/jet/wiki/Query-Result-Mapping-(QRM)#custom-model-types) for more information).
|
||||
|
||||
_Note that it's possible to overwrite default jet generator behavior. All the aspects of generated model and SQLBuilder types can be
|
||||
tailor-made([wiki](https://github.com/go-jet/jet/wiki/Generator#generator-customization))._
|
||||
|
||||
Let's say this is our desired structure made of autogenerated types:
|
||||
```go
|
||||
var dest []struct {
|
||||
model.Actor
|
||||
|
||||
Films []struct {
|
||||
model.Film
|
||||
|
||||
Language model.Language
|
||||
Categories []model.Category
|
||||
}
|
||||
}
|
||||
```
|
||||
`Films` field is a slice because one actor can act in multiple films, and because each film belongs to one language
|
||||
`Langauge` field is just a single model struct. `Film` can belong to multiple categories.
|
||||
_*There is no limitation of how big or nested destination can be._
|
||||
|
||||
Now let's execute above statement on open database connection (or transaction) db and store result into `dest`.
|
||||
|
||||
```go
|
||||
err := stmt.Query(db, &dest)
|
||||
handleError(err)
|
||||
```
|
||||
|
||||
__And that's it.__
|
||||
|
||||
`dest` now contains the list of all actors(with list of films acted, where each film has information about language and list of belonging categories) that acted in films longer than 180 minutes, film language is 'English'
|
||||
and film category is not 'Action'.
|
||||
|
||||
Lets print `dest` as a json to see:
|
||||
```go
|
||||
jsonText, _ := json.MarshalIndent(dest, "", "\t")
|
||||
fmt.Println(string(jsonText))
|
||||
```
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
"ActorID": 1,
|
||||
"FirstName": "Penelope",
|
||||
"LastName": "Guiness",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 499,
|
||||
"Title": "King Evolution",
|
||||
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 3,
|
||||
"RentalRate": 4.99,
|
||||
"Length": 184,
|
||||
"ReplacementCost": 24.99,
|
||||
"Rating": "NC-17",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{Trailers,\"Deleted Scenes\",\"Behind the Scenes\"}",
|
||||
"Fulltext": "'action':5 'action-pack':4 'baloon':21 'boy':10 'chase':16 'evolut':2 'king':1 'lumberjack':13 'madman':18 'must':15 'pack':6 'tale':7",
|
||||
"Language": {
|
||||
"LanguageID": 1,
|
||||
"Name": "English ",
|
||||
"LastUpdate": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
"Categories": [
|
||||
{
|
||||
"CategoryID": 8,
|
||||
"Name": "Family",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ActorID": 3,
|
||||
"FirstName": "Ed",
|
||||
"LastName": "Chase",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 996,
|
||||
"Title": "Young Language",
|
||||
"Description": "A Unbelieveable Yarn of a Boat And a Database Administrator who must Meet a Boy in The First Manned Space Station",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 6,
|
||||
"RentalRate": 0.99,
|
||||
"Length": 183,
|
||||
"ReplacementCost": 9.99,
|
||||
"Rating": "G",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{Trailers,\"Behind the Scenes\"}",
|
||||
"Fulltext": "'administr':12 'boat':8 'boy':17 'databas':11 'first':20 'languag':2 'man':21 'meet':15 'must':14 'space':22 'station':23 'unbeliev':4 'yarn':5 'young':1",
|
||||
"Language": {
|
||||
"LanguageID": 1,
|
||||
"Name": "English ",
|
||||
"LastUpdate": "0001-01-01T00:00:00Z"
|
||||
},
|
||||
"Categories": [
|
||||
{
|
||||
"CategoryID": 6,
|
||||
"Name": "Documentary",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
//...(125 more items)
|
||||
]
|
||||
```
|
||||
|
||||
What if, we also want to have list of films per category and actors per category, where films are longer than 180 minutes, film language is 'English'
|
||||
and film category is not 'Action'.
|
||||
In that case we can reuse above statement `stmt`, and just change our destination:
|
||||
|
||||
```go
|
||||
var dest2 []struct {
|
||||
model.Category
|
||||
|
||||
Films []model.Film
|
||||
Actors []model.Actor
|
||||
}
|
||||
|
||||
err = stmt.Query(db, &dest2)
|
||||
handleError(err)
|
||||
```
|
||||
<details>
|
||||
<summary>Click to see `dest2` json</summary>
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
"CategoryID": 8,
|
||||
"Name": "Family",
|
||||
"LastUpdate": "2006-02-15T09:46:27Z",
|
||||
"Films": [
|
||||
{
|
||||
"FilmID": 499,
|
||||
"Title": "King Evolution",
|
||||
"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 3,
|
||||
"RentalRate": 4.99,
|
||||
"Length": 184,
|
||||
"ReplacementCost": 24.99,
|
||||
"Rating": "NC-17",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{Trailers,\"Deleted Scenes\",\"Behind the Scenes\"}",
|
||||
"Fulltext": "'action':5 'action-pack':4 'baloon':21 'boy':10 'chase':16 'evolut':2 'king':1 'lumberjack':13 'madman':18 'must':15 'pack':6 'tale':7"
|
||||
},
|
||||
{
|
||||
"FilmID": 50,
|
||||
"Title": "Baked Cleopatra",
|
||||
"Description": "A Stunning Drama of a Forensic Psychologist And a Husband who must Overcome a Waitress in A Monastery",
|
||||
"ReleaseYear": 2006,
|
||||
"LanguageID": 1,
|
||||
"RentalDuration": 3,
|
||||
"RentalRate": 2.99,
|
||||
"Length": 182,
|
||||
"ReplacementCost": 20.99,
|
||||
"Rating": "G",
|
||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||
"SpecialFeatures": "{Commentaries,\"Behind the Scenes\"}",
|
||||
"Fulltext": "'bake':1 'cleopatra':2 'drama':5 'forens':8 'husband':12 'monasteri':20 'must':14 'overcom':15 'psychologist':9 'stun':4 'waitress':17"
|
||||
}
|
||||
],
|
||||
"Actors": [
|
||||
{
|
||||
"ActorID": 1,
|
||||
"FirstName": "Penelope",
|
||||
"LastName": "Guiness",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 20,
|
||||
"FirstName": "Lucille",
|
||||
"LastName": "Tracy",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 36,
|
||||
"FirstName": "Burt",
|
||||
"LastName": "Dukakis",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 70,
|
||||
"FirstName": "Michelle",
|
||||
"LastName": "Mcconaughey",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 118,
|
||||
"FirstName": "Cuba",
|
||||
"LastName": "Allen",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 187,
|
||||
"FirstName": "Renee",
|
||||
"LastName": "Ball",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 198,
|
||||
"FirstName": "Mary",
|
||||
"LastName": "Keitel",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
//...
|
||||
]
|
||||
```
|
||||
</details>
|
||||
|
||||
Complete code example can be found at [./examples/quick-start/quick-start.go](./examples/quick-start/quick-start.go)
|
||||
|
||||
|
||||
This example represent probably the most common use case. Detail info about additional statements, features and use cases can be
|
||||
found at project [Wiki](https://github.com/go-jet/jet/wiki) page.
|
||||
|
||||
## Benefits
|
||||
|
||||
What are the benefits of writing SQL in Go using Jet?
|
||||
The biggest benefit is speed. Speed is being improved in 3 major areas:
|
||||
|
||||
##### Speed of development
|
||||
|
||||
Writing SQL queries is faster and easier, as developers will have help of SQL code completion and SQL type safety directly from Go code.
|
||||
Automatic scan to arbitrary structure removes a lot of headache and boilerplate code needed to structure database query result.
|
||||
|
||||
##### Speed of execution
|
||||
|
||||
While ORM libraries can introduce significant performance penalties due to number of round-trips to the database(N+1 query problem),
|
||||
`jet` will always perform better as developers can write complex query and retrieve result with a single database call.
|
||||
Thus handler time lost on latency between server and database can be constant. Handler execution will be proportional
|
||||
only to the query complexity and the number of rows returned from database.
|
||||
|
||||
With Jet, it is even possible to join the whole database and store the whole structured result in one database call.
|
||||
This is exactly what is being done in one of the tests: [TestJoinEverything](https://github.com/go-jet/jet/blob/6706f4b228f51cf810129f57ba90bbdb60b85fe7/tests/postgres/chinook_db_test.go#L187).
|
||||
The whole test database is joined and query result(~10,000 rows) is stored in a structured variable in less than 0.5s.
|
||||
|
||||
##### How quickly bugs are found
|
||||
|
||||
The most expensive bugs are the one discovered on the production, and the least expensive are those found during development.
|
||||
With automatically generated type safe SQL, not only queries are written faster but bugs are found sooner.
|
||||
Let's return to quick start example, and take closer look at a line:
|
||||
```go
|
||||
AND(Film.Length.GT(Int32(180))),
|
||||
```
|
||||
Let's say someone changes column `length` to `duration` from `film` table. The next go build will fail at that line, and
|
||||
the bug will be caught at compile time.
|
||||
|
||||
Let's say someone changes the type of `length` column to some non-integer type. Build will also fail at the same line
|
||||
because integer columns and expressions can be only compared to other integer columns and expressions.
|
||||
|
||||
Build will also fail if someone removes `length` column from `film` table. `Film` field will be omitted from SQL Builder and Model types,
|
||||
next time `jet` generator is run.
|
||||
|
||||
Without Jet these bugs will have to be either caught by tests or by manual testing.
|
||||
|
||||
## Dependencies
|
||||
At the moment Jet dependence only of:
|
||||
- `github.com/lib/pq` _(Used by jet generator to read `PostgreSQL` database information)_
|
||||
- `github.com/go-sql-driver/mysql` _(Used by jet generator to read `MySQL` and `MariaDB` database information)_
|
||||
- `github.com/mattn/go-sqlite3` _(Used by jet generator to read `SQLite` database information)_
|
||||
- `github.com/google/uuid` _(Used in data model files and for debug purposes)_
|
||||
|
||||
To run the tests, additional dependencies are required:
|
||||
- `github.com/pkg/profile`
|
||||
- `github.com/stretchr/testify`
|
||||
- `github.com/google/go-cmp`
|
||||
- `github.com/jackc/pgx/v4`
|
||||
- `github.com/shopspring/decimal`
|
||||
- `github.com/volatiletech/null/v8`
|
||||
|
||||
## Versioning
|
||||
|
||||
[SemVer](http://semver.org/) is used for versioning. For the versions available, take a look at the [releases](https://github.com/go-jet/jet/releases).
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2019-2024 Goran Bjelanovic
|
||||
Licensed under the Apache License, Version 2.0.
|
||||
@@ -1 +0,0 @@
|
||||
theme: jekyll-theme-tactile
|
||||
@@ -1,290 +0,0 @@
|
||||
package main
|
||||
|
||||
//go:generate sh -c "printf 'package main\n\nconst version = \"'%s'\"\n' $(git describe --tags --abbrev=0) > version.go"
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/go-jet/jet/v2/internal/utils/errfmt"
|
||||
"github.com/go-jet/jet/v2/internal/utils/strslice"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
sqlitegen "github.com/go-jet/jet/v2/generator/sqlite"
|
||||
"github.com/go-jet/jet/v2/generator/template"
|
||||
"github.com/go-jet/jet/v2/internal/jet"
|
||||
"github.com/go-jet/jet/v2/mysql"
|
||||
postgres2 "github.com/go-jet/jet/v2/postgres"
|
||||
"github.com/go-jet/jet/v2/sqlite"
|
||||
|
||||
mysqlgen "github.com/go-jet/jet/v2/generator/mysql"
|
||||
postgresgen "github.com/go-jet/jet/v2/generator/postgres"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var (
|
||||
source string
|
||||
|
||||
dsn string
|
||||
host string
|
||||
port int
|
||||
user string
|
||||
password string
|
||||
sslmode string
|
||||
params string
|
||||
dbName string
|
||||
schemaName string
|
||||
|
||||
ignoreTables string
|
||||
ignoreViews string
|
||||
ignoreEnums string
|
||||
|
||||
destDir string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&source, "source", "", "Database system name (postgres, mysql, cockroachdb, mariadb or sqlite)")
|
||||
|
||||
flag.StringVar(&dsn, "dsn", "", `Data source name. Unified format for connecting to database.
|
||||
PostgreSQL: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
|
||||
Example:
|
||||
postgresql://user:pass@localhost:5432/dbname
|
||||
MySQL: https://dev.mysql.com/doc/refman/8.0/en/connecting-using-uri-or-key-value-pairs.html
|
||||
Example:
|
||||
mysql://jet:jet@tcp(localhost:3306)/dvds
|
||||
SQLite: https://www.sqlite.org/c3ref/open.html#urifilenameexamples
|
||||
Example:
|
||||
file://path/to/database/file`)
|
||||
flag.StringVar(&host, "host", "", "Database host path. Used only if dsn is not set. (Example: localhost)")
|
||||
flag.IntVar(&port, "port", 0, "Database port. Used only if dsn is not set.")
|
||||
flag.StringVar(&user, "user", "", "Database user. Used only if dsn is not set.")
|
||||
flag.StringVar(&password, "password", "", "The user’s password. Used only if dsn is not set.")
|
||||
flag.StringVar(&dbName, "dbname", "", "Database name. Used only if dsn is not set.")
|
||||
flag.StringVar(&schemaName, "schema", "public", `Database schema name. (default "public")(PostgreSQL only)`)
|
||||
flag.StringVar(¶ms, "params", "", "Additional connection string parameters(optional). Used only if dsn is not set.")
|
||||
flag.StringVar(&sslmode, "sslmode", "disable", `Whether or not to use SSL. Used only if dsn is not set. (optional)(default "disable")(PostgreSQL only)`)
|
||||
flag.StringVar(&ignoreTables, "ignore-tables", "", `Comma-separated list of tables to ignore`)
|
||||
flag.StringVar(&ignoreViews, "ignore-views", "", `Comma-separated list of views to ignore`)
|
||||
flag.StringVar(&ignoreEnums, "ignore-enums", "", `Comma-separated list of enums to ignore`)
|
||||
|
||||
flag.StringVar(&destDir, "path", "", "Destination dir for files generated.")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if dsn == "" && (source == "" || host == "" || port == 0 || user == "" || dbName == "") {
|
||||
printErrorAndExit("ERROR: required flag(s) missing")
|
||||
}
|
||||
|
||||
source := getSource()
|
||||
ignoreTablesList := parseList(ignoreTables)
|
||||
ignoreViewsList := parseList(ignoreViews)
|
||||
ignoreEnumsList := parseList(ignoreEnums)
|
||||
|
||||
var err error
|
||||
|
||||
switch source {
|
||||
case "postgresql", "postgres", "cockroachdb", "cockroach":
|
||||
generatorTemplate := genTemplate(postgres2.Dialect, ignoreTablesList, ignoreViewsList, ignoreEnumsList)
|
||||
|
||||
if dsn != "" {
|
||||
err = postgresgen.GenerateDSN(dsn, schemaName, destDir, generatorTemplate)
|
||||
break
|
||||
}
|
||||
|
||||
dbConn := postgresgen.DBConnection{
|
||||
Host: host,
|
||||
Port: port,
|
||||
User: user,
|
||||
Password: password,
|
||||
SslMode: sslmode,
|
||||
Params: params,
|
||||
|
||||
DBName: dbName,
|
||||
SchemaName: schemaName,
|
||||
}
|
||||
|
||||
err = postgresgen.Generate(
|
||||
destDir,
|
||||
dbConn,
|
||||
generatorTemplate,
|
||||
)
|
||||
|
||||
case "mysql", "mysqlx", "mariadb":
|
||||
generatorTemplate := genTemplate(mysql.Dialect, ignoreTablesList, ignoreViewsList, ignoreEnumsList)
|
||||
|
||||
if dsn != "" {
|
||||
err = mysqlgen.GenerateDSN(dsn, destDir, generatorTemplate)
|
||||
break
|
||||
}
|
||||
|
||||
dbConn := mysqlgen.DBConnection{
|
||||
Host: host,
|
||||
Port: port,
|
||||
User: user,
|
||||
Password: password,
|
||||
Params: params,
|
||||
DBName: dbName,
|
||||
}
|
||||
|
||||
err = mysqlgen.Generate(
|
||||
destDir,
|
||||
dbConn,
|
||||
generatorTemplate,
|
||||
)
|
||||
case "sqlite":
|
||||
if dsn == "" {
|
||||
printErrorAndExit("ERROR: required -dsn flag missing.")
|
||||
}
|
||||
|
||||
err = sqlitegen.GenerateDSN(
|
||||
dsn,
|
||||
destDir,
|
||||
genTemplate(sqlite.Dialect, ignoreTablesList, ignoreViewsList, ignoreEnumsList),
|
||||
)
|
||||
|
||||
case "":
|
||||
printErrorAndExit("ERROR: required -source or -dsn flag missing.")
|
||||
|
||||
default:
|
||||
printErrorAndExit("ERROR: unknown data source " + source + ". Only postgres, mysql, mariadb and sqlite are supported.")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(errfmt.Trace(err))
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Println("Jet generator", version)
|
||||
fmt.Println()
|
||||
fmt.Println("Usage:")
|
||||
|
||||
order := []string{
|
||||
"source", "dsn", "host", "port", "user", "password", "dbname", "schema", "params", "sslmode",
|
||||
"path",
|
||||
"ignore-tables", "ignore-views", "ignore-enums",
|
||||
}
|
||||
|
||||
for _, name := range order {
|
||||
flagEntry := flag.CommandLine.Lookup(name)
|
||||
fmt.Printf(" -%s\n", flagEntry.Name)
|
||||
fmt.Printf("\t%s\n", flagEntry.Usage)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println(`Example commands:
|
||||
|
||||
$ jet -dsn=postgresql://jet:jet@localhost:5432/jetdb?sslmode=disable -schema=dvds -path=./gen
|
||||
$ jet -dsn=postgres://jet:jet@localhost:26257/jetdb?sslmode=disable -schema=dvds -path=./gen #cockroachdb
|
||||
$ jet -source=postgres -dsn="user=jet password=jet host=localhost port=5432 dbname=jetdb" -schema=dvds -path=./gen
|
||||
$ jet -source=mysql -host=localhost -port=3306 -user=jet -password=jet -dbname=jetdb -path=./gen
|
||||
$ jet -source=sqlite -dsn="file://path/to/sqlite/database/file" -path=./gen
|
||||
`)
|
||||
}
|
||||
|
||||
func printErrorAndExit(error string) {
|
||||
fmt.Println("\n", error)
|
||||
fmt.Println()
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func getSource() string {
|
||||
if source != "" {
|
||||
return strings.TrimSpace(strings.ToLower(source))
|
||||
}
|
||||
|
||||
return detectSchema(dsn)
|
||||
}
|
||||
|
||||
func detectSchema(dsn string) string {
|
||||
match := strings.SplitN(dsn, "://", 2)
|
||||
if len(match) < 2 { // not found
|
||||
return ""
|
||||
}
|
||||
|
||||
protocol := match[0]
|
||||
|
||||
if protocol == "file" {
|
||||
return "sqlite"
|
||||
}
|
||||
|
||||
return strings.ToLower(match[0])
|
||||
}
|
||||
|
||||
func parseList(list string) []string {
|
||||
ret := strings.Split(list, ",")
|
||||
|
||||
for i := 0; i < len(ret); i++ {
|
||||
ret[i] = strings.ToLower(strings.TrimSpace(ret[i]))
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func genTemplate(dialect jet.Dialect, ignoreTables []string, ignoreViews []string, ignoreEnums []string) template.Template {
|
||||
|
||||
shouldSkipTable := func(table metadata.Table) bool {
|
||||
return strslice.Contains(ignoreTables, strings.ToLower(table.Name))
|
||||
}
|
||||
|
||||
shouldSkipView := func(view metadata.Table) bool {
|
||||
return strslice.Contains(ignoreViews, strings.ToLower(view.Name))
|
||||
}
|
||||
|
||||
shouldSkipEnum := func(enum metadata.Enum) bool {
|
||||
return strslice.Contains(ignoreEnums, strings.ToLower(enum.Name))
|
||||
}
|
||||
|
||||
return template.Default(dialect).
|
||||
UseSchema(func(schemaMetaData metadata.Schema) template.Schema {
|
||||
return template.DefaultSchema(schemaMetaData).
|
||||
UseModel(template.DefaultModel().
|
||||
UseTable(func(table metadata.Table) template.TableModel {
|
||||
if shouldSkipTable(table) {
|
||||
return template.TableModel{Skip: true}
|
||||
}
|
||||
return template.DefaultTableModel(table)
|
||||
}).
|
||||
UseView(func(view metadata.Table) template.ViewModel {
|
||||
if shouldSkipView(view) {
|
||||
return template.ViewModel{Skip: true}
|
||||
}
|
||||
return template.DefaultViewModel(view)
|
||||
}).
|
||||
UseEnum(func(enum metadata.Enum) template.EnumModel {
|
||||
if shouldSkipEnum(enum) {
|
||||
return template.EnumModel{Skip: true}
|
||||
}
|
||||
return template.DefaultEnumModel(enum)
|
||||
}),
|
||||
).
|
||||
UseSQLBuilder(template.DefaultSQLBuilder().
|
||||
UseTable(func(table metadata.Table) template.TableSQLBuilder {
|
||||
if shouldSkipTable(table) {
|
||||
return template.TableSQLBuilder{Skip: true}
|
||||
}
|
||||
return template.DefaultTableSQLBuilder(table)
|
||||
}).
|
||||
UseView(func(table metadata.Table) template.ViewSQLBuilder {
|
||||
if shouldSkipView(table) {
|
||||
return template.ViewSQLBuilder{Skip: true}
|
||||
}
|
||||
return template.DefaultViewSQLBuilder(table)
|
||||
}).
|
||||
UseEnum(func(enum metadata.Enum) template.EnumSQLBuilder {
|
||||
if shouldSkipEnum(enum) {
|
||||
return template.EnumSQLBuilder{Skip: true}
|
||||
}
|
||||
return template.DefaultEnumSQLBuilder(enum)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package main
|
||||
|
||||
const version = "v2.11.1"
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
Package jet is a complete solution for efficient and high performance database access, consisting of type-safe SQL builder
|
||||
with code generation and automatic query result data mapping.
|
||||
Jet currently supports PostgreSQL, MySQL, MariaDB and SQLite. Future releases will add support for additional databases.
|
||||
|
||||
# Installation
|
||||
|
||||
Use the command bellow to add jet as a dependency into go.mod project:
|
||||
|
||||
$ go get -u github.com/go-jet/jet/v2
|
||||
|
||||
Jet generator can be installed in one of the following ways:
|
||||
|
||||
1. (Go1.16+) Install jet generator using go install:
|
||||
go install github.com/go-jet/jet/v2/cmd/jet@latest
|
||||
|
||||
2. Install jet generator to GOPATH/bin folder:
|
||||
cd $GOPATH/src/ && GO111MODULE=off go get -u github.com/go-jet/jet/cmd/jet
|
||||
|
||||
3. Install jet generator into specific folder:
|
||||
git clone https://github.com/go-jet/jet.git
|
||||
cd jet && go build -o dir_path ./cmd/jet
|
||||
|
||||
Make sure that the destination folder is added to the PATH environment variable.
|
||||
|
||||
# Usage
|
||||
|
||||
Jet requires already defined database schema(with tables, enums etc), so that jet generator can generate SQL Builder
|
||||
and Model files. File generation is very fast, and can be added as every pre-build step.
|
||||
Sample command:
|
||||
|
||||
jet -dsn=postgresql://user:pass@localhost:5432/jetdb -schema=dvds -path=./.gen
|
||||
|
||||
Before we can write SQL queries in Go, we need to import generated SQL builder and model types:
|
||||
|
||||
import . "some_path/.gen/jetdb/dvds/table"
|
||||
import "some_path/.gen/jetdb/dvds/model"
|
||||
|
||||
To write postgres SQL queries we import:
|
||||
|
||||
. "github.com/go-jet/jet/v2/postgres" // Dot import is used so that Go code resemble as much as native SQL. It is not mandatory.
|
||||
|
||||
Then we can write the SQL query:
|
||||
|
||||
// sub-query
|
||||
rRatingFilms :=
|
||||
SELECT(
|
||||
Film.FilmID,
|
||||
Film.Title,
|
||||
Film.Rating,
|
||||
).FROM(
|
||||
Film,
|
||||
).WHERE(
|
||||
Film.Rating.EQ(enum.FilmRating.R),
|
||||
).AsTable("rFilms")
|
||||
|
||||
// export column from sub-query
|
||||
rFilmID := Film.FilmID.From(rRatingFilms)
|
||||
|
||||
// main-query
|
||||
stmt :=
|
||||
SELECT(
|
||||
Actor.AllColumns,
|
||||
FilmActor.AllColumns,
|
||||
rRatingFilms.AllColumns(),
|
||||
).FROM(
|
||||
rRatingFilms.
|
||||
INNER_JOIN(FilmActor, FilmActor.FilmID.EQ(rFilmID)).
|
||||
INNER_JOIN(Actor, Actor.ActorID.EQ(FilmActor.ActorID)
|
||||
).ORDER_BY(
|
||||
rFilmID,
|
||||
Actor.ActorID,
|
||||
)
|
||||
|
||||
Now we can run the statement and store the result into desired destination:
|
||||
|
||||
var dest []struct {
|
||||
model.Film
|
||||
|
||||
Actors []model.Actor
|
||||
}
|
||||
|
||||
err := stmt.Query(db, &dest)
|
||||
|
||||
We can print a statement to see SQL query and arguments sent to postgres server:
|
||||
|
||||
fmt.Println(stmt.Sql())
|
||||
|
||||
Output:
|
||||
|
||||
SELECT "rFilms"."film.film_id" AS "film.film_id",
|
||||
"rFilms"."film.title" AS "film.title",
|
||||
"rFilms"."film.rating" AS "film.rating",
|
||||
actor.actor_id AS "actor.actor_id",
|
||||
actor.first_name AS "actor.first_name",
|
||||
actor.last_name AS "actor.last_name",
|
||||
actor.last_update AS "actor.last_update",
|
||||
film_actor.actor_id AS "film_actor.actor_id",
|
||||
film_actor.film_id AS "film_actor.film_id",
|
||||
film_actor.last_update AS "film_actor.last_update"
|
||||
FROM (
|
||||
SELECT film.film_id AS "film.film_id",
|
||||
film.title AS "film.title",
|
||||
film.rating AS "film.rating"
|
||||
FROM dvds.film
|
||||
WHERE film.rating = 'R'
|
||||
) AS "rFilms"
|
||||
INNER JOIN dvds.film_actor ON (film_actor.film_id = "rFilms"."film.film_id")
|
||||
INNER JOIN dvds.actor ON (film_actor.actor_id = actor.actor_id)
|
||||
WHERE "rFilms"."film.film_id" < $1
|
||||
ORDER BY "rFilms"."film.film_id" ASC, actor.actor_id ASC;
|
||||
[50]
|
||||
|
||||
If we print destination as json, we'll get:
|
||||
|
||||
[
|
||||
{
|
||||
"FilmID": 8,
|
||||
"Title": "Airport Pollock",
|
||||
"Rating": "R",
|
||||
"Actors": [
|
||||
{
|
||||
"ActorID": 55,
|
||||
"FirstName": "Fay",
|
||||
"LastName": "Kilmer",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 96,
|
||||
"FirstName": "Gene",
|
||||
"LastName": "Willis",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
{
|
||||
"FilmID": 17,
|
||||
"Title": "Alone Trip",
|
||||
"Actors": [
|
||||
{
|
||||
"ActorID": 3,
|
||||
"FirstName": "Ed",
|
||||
"LastName": "Chase",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
{
|
||||
"ActorID": 12,
|
||||
"FirstName": "Karl",
|
||||
"LastName": "Berry",
|
||||
"LastUpdate": "2013-05-26T14:47:57.62Z"
|
||||
},
|
||||
...
|
||||
...
|
||||
]
|
||||
|
||||
Detail info about all statements, features and use cases can be
|
||||
found at project wiki page - https://github.com/go-jet/jet/wiki.
|
||||
*/
|
||||
package jet
|
||||
@@ -1,12 +0,0 @@
|
||||
|
||||
# Quick start example
|
||||
|
||||
This package contains sample usage for Jet framework.
|
||||
|
||||
Jet generated files of interest are in `./gen` folder.
|
||||
|
||||
`quick-start.go` - contains code explained at main [README.md](../../README.md#quick-start),
|
||||
with a difference of redirecting json output to files(`dest.json` and `dest2.json`) rather then to a
|
||||
standard output.
|
||||
|
||||
`./gen`, `dest.json` and `dest2.json` - added into git for presentation purposes.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
@@ -1,118 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
// dot import so that jet go code would resemble as much as native SQL
|
||||
// dot import is not mandatory
|
||||
. "github.com/go-jet/jet/v2/examples/quick-start/.gen/jetdb/dvds/table"
|
||||
. "github.com/go-jet/jet/v2/postgres"
|
||||
|
||||
"github.com/go-jet/jet/v2/examples/quick-start/.gen/jetdb/dvds/model"
|
||||
)
|
||||
|
||||
const (
|
||||
host = "localhost"
|
||||
port = 5432
|
||||
user = "jet"
|
||||
password = "jet"
|
||||
dbName = "jetdb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to database
|
||||
var connectString = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbName)
|
||||
|
||||
db, err := sql.Open("postgres", connectString)
|
||||
panicOnError(err)
|
||||
defer db.Close()
|
||||
|
||||
// Write query
|
||||
stmt := SELECT(
|
||||
Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate,
|
||||
Film.AllColumns,
|
||||
Language.AllColumns.Except(Language.LastUpdate),
|
||||
Category.AllColumns,
|
||||
).FROM(
|
||||
Actor.
|
||||
INNER_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.ActorID)).
|
||||
INNER_JOIN(Film, Film.FilmID.EQ(FilmActor.FilmID)).
|
||||
INNER_JOIN(Language, Language.LanguageID.EQ(Film.LanguageID)).
|
||||
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
|
||||
INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
|
||||
).WHERE(
|
||||
Language.Name.EQ(Char(20)("English")).
|
||||
AND(Category.Name.NOT_EQ(Text("Action"))).
|
||||
AND(Film.Length.GT(Int(180))),
|
||||
).ORDER_BY(
|
||||
Actor.ActorID.ASC(),
|
||||
Film.FilmID.ASC(),
|
||||
)
|
||||
|
||||
// Execute query and store result
|
||||
var dest []struct {
|
||||
model.Actor
|
||||
|
||||
Films []struct {
|
||||
model.Film
|
||||
|
||||
Language model.Language
|
||||
Categories []model.Category
|
||||
}
|
||||
}
|
||||
|
||||
err = stmt.Query(db, &dest)
|
||||
panicOnError(err)
|
||||
|
||||
printStatementInfo(stmt)
|
||||
jsonSave("./dest.json", dest)
|
||||
|
||||
// New Destination
|
||||
|
||||
var dest2 []struct {
|
||||
model.Category
|
||||
|
||||
Films []model.Film
|
||||
Actors []model.Actor
|
||||
}
|
||||
|
||||
err = stmt.Query(db, &dest2)
|
||||
panicOnError(err)
|
||||
|
||||
jsonSave("./dest2.json", dest2)
|
||||
}
|
||||
|
||||
func jsonSave(path string, v interface{}) {
|
||||
jsonText, _ := json.MarshalIndent(v, "", "\t")
|
||||
|
||||
err := os.WriteFile(path, jsonText, 0600)
|
||||
|
||||
panicOnError(err)
|
||||
}
|
||||
|
||||
func printStatementInfo(stmt SelectStatement) {
|
||||
query, args := stmt.Sql()
|
||||
|
||||
fmt.Println("Parameterized query: ")
|
||||
fmt.Println("==============================")
|
||||
fmt.Println(query)
|
||||
fmt.Println("Arguments: ")
|
||||
fmt.Println(args)
|
||||
|
||||
debugSQL := stmt.DebugSql()
|
||||
|
||||
fmt.Println("\n\nDebug sql: ")
|
||||
fmt.Println("==============================")
|
||||
fmt.Println(debugSQL)
|
||||
}
|
||||
|
||||
func panicOnError(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package metadata
|
||||
|
||||
// Column struct
|
||||
type Column struct {
|
||||
Name string `sql:"primary_key"`
|
||||
IsPrimaryKey bool
|
||||
IsNullable bool
|
||||
IsGenerated bool
|
||||
HasDefault bool
|
||||
DataType DataType
|
||||
Comment string
|
||||
}
|
||||
|
||||
// DataTypeKind is database type kind(base, enum, user-defined, array)
|
||||
type DataTypeKind string
|
||||
|
||||
// DataTypeKind possible values
|
||||
const (
|
||||
BaseType DataTypeKind = "base"
|
||||
EnumType DataTypeKind = "enum"
|
||||
UserDefinedType DataTypeKind = "user-defined"
|
||||
ArrayType DataTypeKind = "array"
|
||||
RangeType DataTypeKind = "range"
|
||||
)
|
||||
|
||||
// DataType contains information about column data type
|
||||
type DataType struct {
|
||||
Name string
|
||||
Kind DataTypeKind
|
||||
IsUnsigned bool
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TableType is type of database table(view or base)
|
||||
type TableType string
|
||||
|
||||
// SQL table types
|
||||
const (
|
||||
BaseTable TableType = "BASE TABLE"
|
||||
ViewTable TableType = "VIEW"
|
||||
)
|
||||
|
||||
// DialectQuerySet is set of methods necessary to retrieve dialect metadata information
|
||||
type DialectQuerySet interface {
|
||||
GetTablesMetaData(db *sql.DB, schemaName string, tableType TableType) ([]Table, error)
|
||||
GetEnumsMetaData(db *sql.DB, schemaName string) ([]Enum, error)
|
||||
}
|
||||
|
||||
// GetSchema retrieves Schema information from database
|
||||
func GetSchema(db *sql.DB, querySet DialectQuerySet, schemaName string) (Schema, error) {
|
||||
tablesMetaData, err := querySet.GetTablesMetaData(db, schemaName, BaseTable)
|
||||
if err != nil {
|
||||
return Schema{}, fmt.Errorf("failed to get %s tables metadata: %w", schemaName, err)
|
||||
}
|
||||
|
||||
viewMetaData, err := querySet.GetTablesMetaData(db, schemaName, ViewTable)
|
||||
if err != nil {
|
||||
return Schema{}, fmt.Errorf("failed to get %s view metadata: %w", schemaName, err)
|
||||
}
|
||||
|
||||
enumsMetaData, err := querySet.GetEnumsMetaData(db, schemaName)
|
||||
if err != nil {
|
||||
return Schema{}, fmt.Errorf("failed to get %s enum metadata: %w", schemaName, err)
|
||||
}
|
||||
|
||||
ret := Schema{
|
||||
Name: schemaName,
|
||||
TablesMetaData: tablesMetaData,
|
||||
ViewsMetaData: viewMetaData,
|
||||
EnumsMetaData: enumsMetaData,
|
||||
}
|
||||
|
||||
fmt.Println(" FOUND", len(ret.TablesMetaData), "table(s),", len(ret.ViewsMetaData), "view(s),",
|
||||
len(ret.EnumsMetaData), "enum(s)")
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package metadata
|
||||
|
||||
// Enum metadata struct
|
||||
type Enum struct {
|
||||
Name string `sql:"primary_key"`
|
||||
Comment string
|
||||
Values []string
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package metadata
|
||||
|
||||
// Schema struct
|
||||
type Schema struct {
|
||||
Name string
|
||||
TablesMetaData []Table
|
||||
ViewsMetaData []Table
|
||||
EnumsMetaData []Enum
|
||||
}
|
||||
|
||||
// IsEmpty returns true if schema info does not contain any table, views or enums metadata
|
||||
func (s Schema) IsEmpty() bool {
|
||||
return len(s.TablesMetaData) == 0 && len(s.ViewsMetaData) == 0 && len(s.EnumsMetaData) == 0
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package metadata
|
||||
|
||||
// Table metadata struct
|
||||
type Table struct {
|
||||
Name string `sql:"primary_key"`
|
||||
Comment string
|
||||
Columns []Column
|
||||
}
|
||||
|
||||
// MutableColumns returns list of mutable columns for table
|
||||
func (t Table) MutableColumns() []Column {
|
||||
var ret []Column
|
||||
|
||||
for _, column := range t.Columns {
|
||||
if column.IsPrimaryKey || column.IsGenerated {
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, column)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/go-jet/jet/v2/generator/template"
|
||||
"github.com/go-jet/jet/v2/mysql"
|
||||
mysqldr "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
const mysqlMaxConns = 10
|
||||
|
||||
// DBConnection contains MySQL connection details
|
||||
type DBConnection struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
Params string
|
||||
|
||||
DBName string
|
||||
}
|
||||
|
||||
// Generate generates jet files at destination dir from database connection details
|
||||
func Generate(destDir string, dbConn DBConnection, generatorTemplate ...template.Template) error {
|
||||
connectionString := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", dbConn.User, dbConn.Password, dbConn.Host, dbConn.Port, dbConn.DBName)
|
||||
if dbConn.Params != "" {
|
||||
connectionString += "?" + dbConn.Params
|
||||
}
|
||||
|
||||
db, err := openConnection(connectionString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open db connection: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = generate(db, dbConn.DBName, destDir, generatorTemplate...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateDSN opens connection via DSN string and does everything what Generate does.
|
||||
func GenerateDSN(dsn, destDir string, templates ...template.Template) error {
|
||||
// Special case for go mysql driver. It does not understand schema,
|
||||
// so we need to trim it before passing to generator
|
||||
// https://github.com/go-sql-driver/mysql#dsn-data-source-name
|
||||
idx := strings.Index(dsn, "://")
|
||||
if idx != -1 {
|
||||
dsn = dsn[idx+len("://"):]
|
||||
}
|
||||
|
||||
cfg, err := mysqldr.ParseDSN(dsn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse DSN: %w", err)
|
||||
}
|
||||
if cfg.DBName == "" {
|
||||
return errors.New("database name is required")
|
||||
}
|
||||
|
||||
db, err := openConnection(dsn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open db connection: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = generate(db, cfg.DBName, destDir, templates...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func openConnection(connectionString string) (*sql.DB, error) {
|
||||
fmt.Println("Connecting to MySQL database...")
|
||||
db, err := sql.Open("mysql", connectionString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open mysql connection: %w", err)
|
||||
}
|
||||
|
||||
db.SetMaxOpenConns(mysqlMaxConns)
|
||||
db.SetMaxIdleConns(mysqlMaxConns)
|
||||
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to ping database: %w", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func generate(db *sql.DB, dbName, destDir string, templates ...template.Template) error {
|
||||
fmt.Println("Retrieving database information...")
|
||||
// No schemas in MySQL
|
||||
schemaMetaData, err := metadata.GetSchema(db, &mySqlQuerySet{}, dbName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get '%s' database metadata: %w", dbName, err)
|
||||
}
|
||||
|
||||
genTemplate := template.Default(mysql.Dialect)
|
||||
if len(templates) > 0 {
|
||||
genTemplate = templates[0]
|
||||
}
|
||||
|
||||
err = template.ProcessSchema(destDir, schemaMetaData, genTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process '%s' database: %w", schemaMetaData.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/go-jet/jet/v2/qrm"
|
||||
)
|
||||
|
||||
// mySqlQuerySet is dialect query set for MySQL
|
||||
type mySqlQuerySet struct{}
|
||||
|
||||
func (m mySqlQuerySet) GetTablesMetaData(db *sql.DB, schemaName string, tableType metadata.TableType) ([]metadata.Table, error) {
|
||||
query := `
|
||||
SELECT
|
||||
t.table_name as "table.name",
|
||||
col.COLUMN_NAME AS "column.Name",
|
||||
col.COLUMN_DEFAULT IS NOT NULL as "column.HasDefault",
|
||||
col.IS_NULLABLE = "YES" AS "column.IsNullable",
|
||||
col.COLUMN_COMMENT AS "column.Comment",
|
||||
COALESCE(pk.IsPrimaryKey, 0) AS "column.IsPrimaryKey",
|
||||
IF (col.COLUMN_TYPE = 'tinyint(1)',
|
||||
'boolean',
|
||||
IF (col.DATA_TYPE = 'enum',
|
||||
CONCAT(col.TABLE_NAME, '_', col.COLUMN_NAME),
|
||||
col.DATA_TYPE)
|
||||
) AS "dataType.Name",
|
||||
IF (col.DATA_TYPE = 'enum', 'enum', 'base') AS "dataType.Kind",
|
||||
col.COLUMN_TYPE LIKE '%unsigned%' AS "dataType.IsUnsigned"
|
||||
FROM INFORMATION_SCHEMA.tables AS t
|
||||
INNER JOIN
|
||||
information_schema.columns AS col
|
||||
ON t.table_schema = col.table_schema AND t.table_name = col.table_name
|
||||
LEFT JOIN (
|
||||
SELECT k.column_name, 1 AS IsPrimaryKey, k.table_name
|
||||
FROM information_schema.table_constraints t
|
||||
JOIN information_schema.key_column_usage k USING(constraint_name, table_schema, table_name)
|
||||
WHERE t.table_schema = ?
|
||||
AND t.constraint_type = 'PRIMARY KEY'
|
||||
) AS pk ON col.COLUMN_NAME = pk.column_name AND col.table_name = pk.table_name
|
||||
WHERE t.table_schema = ?
|
||||
AND t.table_type = ?
|
||||
ORDER BY
|
||||
t.table_name,
|
||||
col.ordinal_position;
|
||||
`
|
||||
|
||||
var tables []metadata.Table
|
||||
|
||||
_, err := qrm.Query(context.Background(), db, query, []interface{}{schemaName, schemaName, tableType}, &tables)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query column meta data: %w", err)
|
||||
}
|
||||
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func (m mySqlQuerySet) GetEnumsMetaData(db *sql.DB, schemaName string) ([]metadata.Enum, error) {
|
||||
query := `
|
||||
SELECT (CASE c.DATA_TYPE WHEN 'enum' then CONCAT(c.TABLE_NAME, '_', c.COLUMN_NAME) ELSE '' END ) as "name",
|
||||
SUBSTRING(c.COLUMN_TYPE,5) as "values"
|
||||
FROM information_schema.columns as c
|
||||
INNER JOIN information_schema.tables as t on (t.table_schema = c.table_schema AND t.table_name = c.table_name)
|
||||
WHERE c.table_schema = ? AND DATA_TYPE = 'enum';
|
||||
`
|
||||
var queryResult []struct {
|
||||
Name string
|
||||
Values string
|
||||
}
|
||||
|
||||
_, err := qrm.Query(context.Background(), db, query, []interface{}{schemaName}, &queryResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query enums meta data: %w", err)
|
||||
}
|
||||
|
||||
var ret []metadata.Enum
|
||||
|
||||
for _, result := range queryResult {
|
||||
enumValues := strings.Replace(result.Values[1:len(result.Values)-1], "'", "", -1)
|
||||
|
||||
ret = append(ret, metadata.Enum{
|
||||
Name: result.Name,
|
||||
Values: strings.Split(enumValues, ","),
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/go-jet/jet/v2/generator/template"
|
||||
"github.com/go-jet/jet/v2/postgres"
|
||||
"github.com/jackc/pgconn"
|
||||
)
|
||||
|
||||
// DBConnection contains postgres connection details
|
||||
type DBConnection struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
SslMode string
|
||||
Params string
|
||||
|
||||
DBName string
|
||||
SchemaName string
|
||||
}
|
||||
|
||||
// Generate generates jet files at destination dir from database connection details
|
||||
func Generate(destDir string, dbConn DBConnection, genTemplate ...template.Template) (err error) {
|
||||
dsn := fmt.Sprintf("postgresql://%s:%s@%s:%s/%s?sslmode=%s",
|
||||
url.PathEscape(dbConn.User),
|
||||
url.PathEscape(dbConn.Password),
|
||||
dbConn.Host,
|
||||
strconv.Itoa(dbConn.Port),
|
||||
url.PathEscape(dbConn.DBName),
|
||||
dbConn.SslMode,
|
||||
)
|
||||
|
||||
return GenerateDSN(dsn, dbConn.SchemaName, destDir, genTemplate...)
|
||||
}
|
||||
|
||||
// GenerateDSN generates jet files using dsn connection string
|
||||
func GenerateDSN(dsn, schema, destDir string, templates ...template.Template) error {
|
||||
cfg, err := pgconn.ParseConfig(dsn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse config: %w", err)
|
||||
}
|
||||
if cfg.Database == "" {
|
||||
return fmt.Errorf("database name is required")
|
||||
}
|
||||
db, err := openConnection(dsn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open db connection: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
fmt.Println("Retrieving schema information...")
|
||||
generatorTemplate := template.Default(postgres.Dialect)
|
||||
if len(templates) > 0 {
|
||||
generatorTemplate = templates[0]
|
||||
}
|
||||
|
||||
schemaMetadata, err := metadata.GetSchema(db, &postgresQuerySet{}, schema)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get '%s' schema metadata: %w", schema, err)
|
||||
}
|
||||
|
||||
dirPath := path.Join(destDir, cfg.Database)
|
||||
|
||||
err = template.ProcessSchema(dirPath, schemaMetadata, generatorTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate schema %s: %d", schemaMetadata.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func openConnection(dsn string) (*sql.DB, error) {
|
||||
fmt.Println("Connecting to postgres database...")
|
||||
|
||||
db, err := sql.Open("postgres", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open db connection: %w", err)
|
||||
}
|
||||
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to ping database: %w", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/go-jet/jet/v2/qrm"
|
||||
)
|
||||
|
||||
// postgresQuerySet is dialect query set for PostgreSQL
|
||||
type postgresQuerySet struct{}
|
||||
|
||||
func (p postgresQuerySet) GetTablesMetaData(db *sql.DB, schemaName string, tableType metadata.TableType) ([]metadata.Table, error) {
|
||||
query := `
|
||||
SELECT table_name as "table.name", obj_description((quote_ident(table_schema)||'.'||quote_ident(table_name))::regclass) as "table.comment"
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = $1 and table_type = $2
|
||||
ORDER BY table_name;
|
||||
`
|
||||
var tables []metadata.Table
|
||||
|
||||
_, err := qrm.Query(context.Background(), db, query, []interface{}{schemaName, tableType}, &tables)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query %s metadata: %w", tableType, err)
|
||||
}
|
||||
|
||||
// add materialized views separately, because materialized views are not part of standard information schema
|
||||
if tableType == metadata.ViewTable {
|
||||
matViewQuery := `
|
||||
select matviewname as "table.name"
|
||||
from pg_matviews
|
||||
where schemaname = $1;
|
||||
`
|
||||
var matViews []metadata.Table
|
||||
|
||||
_, err := qrm.Query(context.Background(), db, matViewQuery, []interface{}{schemaName}, &matViews)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query materialized view metadata: %w", err)
|
||||
}
|
||||
|
||||
tables = append(tables, matViews...)
|
||||
}
|
||||
|
||||
for i := range tables {
|
||||
tables[i].Columns, err = getColumnsMetaData(db, schemaName, tables[i].Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query %s columns metadata: %w", tableType, err)
|
||||
}
|
||||
}
|
||||
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func getColumnsMetaData(db *sql.DB, schemaName string, tableName string) ([]metadata.Column, error) {
|
||||
query := `
|
||||
select
|
||||
attr.attname as "column.Name",
|
||||
col_description(attr.attrelid, attr.attnum) as "column.Comment",
|
||||
exists(
|
||||
select 1
|
||||
from pg_catalog.pg_index indx
|
||||
where attr.attrelid = indx.indrelid and attr.attnum = any(indx.indkey) and indx.indisprimary
|
||||
) as "column.IsPrimaryKey",
|
||||
not attr.attnotnull as "column.isNullable",
|
||||
attr.attgenerated = 's' as "column.isGenerated",
|
||||
attr.atthasdef as "column.hasDefault",
|
||||
(case
|
||||
when tp.typtype = 'b' AND tp.typcategory <> 'A' then 'base'
|
||||
when tp.typtype = 'b' AND tp.typcategory = 'A' then 'array'
|
||||
when tp.typtype = 'd' then 'base'
|
||||
when tp.typtype = 'e' then 'enum'
|
||||
when tp.typtype = 'r' then 'range'
|
||||
end) as "dataType.Kind",
|
||||
(case when tp.typtype = 'd' then (select pg_type.typname from pg_catalog.pg_type where pg_type.oid = tp.typbasetype)
|
||||
when tp.typcategory = 'A' then pg_catalog.format_type(attr.atttypid, attr.atttypmod)
|
||||
else tp.typname
|
||||
end) as "dataType.Name",
|
||||
false as "dataType.isUnsigned"
|
||||
from pg_catalog.pg_attribute as attr
|
||||
join pg_catalog.pg_class as cls on cls.oid = attr.attrelid
|
||||
join pg_catalog.pg_namespace as ns on ns.oid = cls.relnamespace
|
||||
join pg_catalog.pg_type as tp on tp.oid = attr.atttypid
|
||||
where
|
||||
ns.nspname = $1 and
|
||||
cls.relname = $2 and
|
||||
not attr.attisdropped and
|
||||
attr.attnum > 0
|
||||
order by
|
||||
attr.attnum;
|
||||
`
|
||||
var columns []metadata.Column
|
||||
_, err := qrm.Query(context.Background(), db, query, []interface{}{schemaName, tableName}, &columns)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query '%s' columns metadata: %w", tableName, err)
|
||||
}
|
||||
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
func (p postgresQuerySet) GetEnumsMetaData(db *sql.DB, schemaName string) ([]metadata.Enum, error) {
|
||||
query := `
|
||||
SELECT t.typname as "enum.name",
|
||||
obj_description(t.oid) as "enum.comment",
|
||||
e.enumlabel as "values"
|
||||
FROM pg_catalog.pg_type t
|
||||
JOIN pg_catalog.pg_enum e on t.oid = e.enumtypid
|
||||
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE n.nspname = $1
|
||||
ORDER BY n.nspname, t.typname, e.enumsortorder;`
|
||||
|
||||
var result []metadata.Enum
|
||||
|
||||
_, err := qrm.Query(context.Background(), db, query, []interface{}{schemaName}, &result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query enums metadata for schema '%s': %w", schemaName, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/go-jet/jet/v2/internal/utils/semantic"
|
||||
"github.com/go-jet/jet/v2/qrm"
|
||||
)
|
||||
|
||||
// sqliteQuerySet is dialect query set for SQLite
|
||||
type sqliteQuerySet struct{}
|
||||
|
||||
func (p sqliteQuerySet) GetTablesMetaData(db *sql.DB, schemaName string, tableType metadata.TableType) ([]metadata.Table, error) {
|
||||
query := `
|
||||
SELECT name as "table.name"
|
||||
FROM sqlite_master
|
||||
WHERE type=? AND name != 'sqlite_sequence'
|
||||
ORDER BY name;
|
||||
`
|
||||
sqlTableType := "table"
|
||||
|
||||
if tableType == metadata.ViewTable {
|
||||
sqlTableType = "view"
|
||||
}
|
||||
|
||||
var tables []metadata.Table
|
||||
|
||||
_, err := qrm.Query(context.Background(), db, query, []interface{}{sqlTableType}, &tables)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query %s metadata: %w", schemaName, err)
|
||||
}
|
||||
|
||||
for i := range tables {
|
||||
tables[i].Columns, err = p.GetTableColumnsMetaData(db, schemaName, tables[i].Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query column metadata: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func getTableInfoQuery(db *sql.DB) (string, error) {
|
||||
var version string
|
||||
err := db.QueryRow("select sqlite_version();").Scan(&version)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get sqlite version: %w", err)
|
||||
}
|
||||
|
||||
sqliteVersion, err := semantic.VersionFromString(version)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't parse sqlite version: %w", err)
|
||||
}
|
||||
|
||||
// generated columns were added in version 3.26.0
|
||||
if sqliteVersion.Lt(semantic.Version{Major: 3, Minor: 26, Patch: 0}) {
|
||||
return `select * from pragma_table_info(?);`, nil
|
||||
}
|
||||
|
||||
return `select * from pragma_table_xinfo(?);`, nil
|
||||
}
|
||||
|
||||
func (p sqliteQuerySet) GetTableColumnsMetaData(db *sql.DB, schemaName string, tableName string) ([]metadata.Column, error) {
|
||||
|
||||
tableInfoQuery, err := getTableInfoQuery(db)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var columnInfos []struct {
|
||||
Name string
|
||||
Type string
|
||||
NotNull int32
|
||||
DfltValue string
|
||||
Pk int32
|
||||
Hidden int32
|
||||
}
|
||||
|
||||
_, err = qrm.Query(context.Background(), db, tableInfoQuery, []interface{}{tableName}, &columnInfos)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query '%s' column metadata: %w", tableName, err)
|
||||
}
|
||||
|
||||
var columns []metadata.Column
|
||||
|
||||
for _, columnInfo := range columnInfos {
|
||||
columnType := strings.TrimSuffix(getColumnType(columnInfo.Type), " GENERATED ALWAYS")
|
||||
isGenerated := columnInfo.Hidden == 2 || columnInfo.Hidden == 3 // stored or virtual column
|
||||
hasDefault := columnInfo.DfltValue != ""
|
||||
|
||||
columns = append(columns, metadata.Column{
|
||||
Name: columnInfo.Name,
|
||||
IsPrimaryKey: columnInfo.Pk != 0,
|
||||
IsNullable: columnInfo.NotNull != 1,
|
||||
IsGenerated: isGenerated,
|
||||
HasDefault: hasDefault,
|
||||
DataType: metadata.DataType{
|
||||
Name: columnType,
|
||||
Kind: metadata.BaseType,
|
||||
IsUnsigned: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
// will convert VARCHAR(10) -> VARCHAR, etc...
|
||||
func getColumnType(columnType string) string {
|
||||
return strings.TrimSpace(strings.Split(columnType, "(")[0])
|
||||
}
|
||||
|
||||
func (p sqliteQuerySet) GetEnumsMetaData(db *sql.DB, schemaName string) ([]metadata.Enum, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/go-jet/jet/v2/generator/template"
|
||||
"github.com/go-jet/jet/v2/sqlite"
|
||||
)
|
||||
|
||||
// GenerateDSN generates jet files using dsn connection string
|
||||
func GenerateDSN(dsn, destDir string, templates ...template.Template) error {
|
||||
db, err := sql.Open("sqlite3", dsn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open sqlite connection: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
fmt.Println("Retrieving schema information...")
|
||||
|
||||
generatorTemplate := template.Default(sqlite.Dialect)
|
||||
if len(templates) > 0 {
|
||||
generatorTemplate = templates[0]
|
||||
}
|
||||
|
||||
schemaMetadata, err := metadata.GetSchema(db, &sqliteQuerySet{}, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to query database metadata: %w", err)
|
||||
}
|
||||
|
||||
err = template.ProcessSchema(destDir, schemaMetadata, generatorTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process database %s: %w", schemaMetadata.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package template
|
||||
|
||||
var autoGenWarningTemplate = `
|
||||
//
|
||||
// Code generated by go-jet DO NOT EDIT.
|
||||
//
|
||||
// WARNING: Changes to this file may cause incorrect behavior
|
||||
// and will be lost if the code is regenerated
|
||||
//
|
||||
|
||||
`
|
||||
|
||||
var tableSQLBuilderTemplate = `
|
||||
{{define "column-list" -}}
|
||||
{{- range $i, $c := . }}
|
||||
{{- $field := columnField $c}}
|
||||
{{- if gt $i 0 }}, {{end}}{{$field.Name}}Column
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
|
||||
package {{package}}
|
||||
|
||||
import (
|
||||
"github.com/go-jet/jet/v2/{{dialect.PackageName}}"
|
||||
)
|
||||
|
||||
var {{tableTemplate.InstanceName}} = new{{tableTemplate.TypeName}}("{{schemaName}}", "{{.Name}}", "{{tableTemplate.DefaultAlias}}")
|
||||
|
||||
{{golangComment .Comment}}
|
||||
type {{structImplName}} struct {
|
||||
{{dialect.PackageName}}.Table
|
||||
|
||||
// Columns
|
||||
{{- range $i, $c := .Columns}}
|
||||
{{- $field := columnField $c}}
|
||||
{{$field.Name}} {{dialect.PackageName}}.Column{{$field.Type}} {{golangComment .Comment}}
|
||||
{{- end}}
|
||||
|
||||
AllColumns {{dialect.PackageName}}.ColumnList
|
||||
MutableColumns {{dialect.PackageName}}.ColumnList
|
||||
}
|
||||
|
||||
type {{tableTemplate.TypeName}} struct {
|
||||
{{structImplName}}
|
||||
|
||||
{{toUpper insertedRowAlias}} {{structImplName}}
|
||||
}
|
||||
|
||||
// AS creates new {{tableTemplate.TypeName}} with assigned alias
|
||||
func (a {{tableTemplate.TypeName}}) AS(alias string) *{{tableTemplate.TypeName}} {
|
||||
return new{{tableTemplate.TypeName}}(a.SchemaName(), a.TableName(), alias)
|
||||
}
|
||||
|
||||
// Schema creates new {{tableTemplate.TypeName}} with assigned schema name
|
||||
func (a {{tableTemplate.TypeName}}) FromSchema(schemaName string) *{{tableTemplate.TypeName}} {
|
||||
return new{{tableTemplate.TypeName}}(schemaName, a.TableName(), a.Alias())
|
||||
}
|
||||
|
||||
// WithPrefix creates new {{tableTemplate.TypeName}} with assigned table prefix
|
||||
func (a {{tableTemplate.TypeName}}) WithPrefix(prefix string) *{{tableTemplate.TypeName}} {
|
||||
return new{{tableTemplate.TypeName}}(a.SchemaName(), prefix+a.TableName(), a.TableName())
|
||||
}
|
||||
|
||||
// WithSuffix creates new {{tableTemplate.TypeName}} with assigned table suffix
|
||||
func (a {{tableTemplate.TypeName}}) WithSuffix(suffix string) *{{tableTemplate.TypeName}} {
|
||||
return new{{tableTemplate.TypeName}}(a.SchemaName(), a.TableName()+suffix, a.TableName())
|
||||
}
|
||||
|
||||
func new{{tableTemplate.TypeName}}(schemaName, tableName, alias string) *{{tableTemplate.TypeName}} {
|
||||
return &{{tableTemplate.TypeName}}{
|
||||
{{structImplName}}: new{{tableTemplate.TypeName}}Impl(schemaName, tableName, alias),
|
||||
{{toUpper insertedRowAlias}}: new{{tableTemplate.TypeName}}Impl("", "{{insertedRowAlias}}", ""),
|
||||
}
|
||||
}
|
||||
|
||||
func new{{tableTemplate.TypeName}}Impl(schemaName, tableName, alias string) {{structImplName}} {
|
||||
var (
|
||||
{{- range $i, $c := .Columns}}
|
||||
{{- $field := columnField $c}}
|
||||
{{$field.Name}}Column = {{dialect.PackageName}}.{{$field.Type}}Column("{{$c.Name}}")
|
||||
{{- end}}
|
||||
allColumns = {{dialect.PackageName}}.ColumnList{ {{template "column-list" .Columns}} }
|
||||
mutableColumns = {{dialect.PackageName}}.ColumnList{ {{template "column-list" .MutableColumns}} }
|
||||
)
|
||||
|
||||
return {{structImplName}}{
|
||||
Table: {{dialect.PackageName}}.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
{{- range $i, $c := .Columns}}
|
||||
{{- $field := columnField $c}}
|
||||
{{$field.Name}}: {{$field.Name}}Column,
|
||||
{{- end}}
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var tableSqlBuilderSetSchemaTemplate = `package {{package}}
|
||||
|
||||
// UseSchema sets a new schema name for all generated {{type}} SQL builder types. It is recommended to invoke
|
||||
// this method only once at the beginning of the program.
|
||||
func UseSchema(schema string) {
|
||||
{{- range .}}
|
||||
{{ .InstanceName }} = {{ .InstanceName }}.FromSchema(schema)
|
||||
{{- end}}
|
||||
}
|
||||
`
|
||||
|
||||
var tableModelFileTemplate = `package {{package}}
|
||||
|
||||
{{ with modelImports }}
|
||||
import (
|
||||
{{- range .}}
|
||||
"{{.}}"
|
||||
{{- end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{$modelTableTemplate := tableTemplate}}
|
||||
{{golangComment .Comment}}
|
||||
type {{$modelTableTemplate.TypeName}} struct {
|
||||
{{- range .Columns}}
|
||||
{{- $field := structField .}}
|
||||
{{$field.Name}} {{$field.Type.Name}} ` + "{{$field.TagsString}}" + ` {{golangComment .Comment}}
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
var enumSQLBuilderTemplate = `package {{package}}
|
||||
|
||||
import "github.com/go-jet/jet/v2/{{dialect.PackageName}}"
|
||||
|
||||
{{golangComment .Comment}}
|
||||
var {{enumTemplate.InstanceName}} = &struct {
|
||||
{{- range $index, $value := .Values}}
|
||||
{{enumValueName $value}} {{dialect.PackageName}}.StringExpression
|
||||
{{- end}}
|
||||
} {
|
||||
{{- range $index, $value := .Values}}
|
||||
{{enumValueName $value}}: {{dialect.PackageName}}.NewEnumValue("{{$value}}"),
|
||||
{{- end}}
|
||||
}
|
||||
`
|
||||
|
||||
var enumModelTemplate = `package {{package}}
|
||||
{{- $enumTemplate := enumTemplate}}
|
||||
|
||||
import "errors"
|
||||
|
||||
{{golangComment .Comment}}
|
||||
type {{$enumTemplate.TypeName}} string
|
||||
|
||||
const (
|
||||
{{- range $_, $value := .Values}}
|
||||
{{valueName $value}} {{$enumTemplate.TypeName}} = "{{$value}}"
|
||||
{{- end}}
|
||||
)
|
||||
|
||||
var {{$enumTemplate.TypeName}}AllValues = []{{$enumTemplate.TypeName}} {
|
||||
{{- range $_, $value := .Values}}
|
||||
{{valueName $value}},
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
func (e *{{$enumTemplate.TypeName}}) Scan(value interface{}) error {
|
||||
var enumValue string
|
||||
switch val := value.(type) {
|
||||
case string:
|
||||
enumValue = val
|
||||
case []byte:
|
||||
enumValue = string(val)
|
||||
default:
|
||||
return errors.New("jet: Invalid scan value for AllTypesEnum enum. Enum value has to be of type string or []byte")
|
||||
}
|
||||
|
||||
switch enumValue {
|
||||
{{- range $_, $value := .Values}}
|
||||
case "{{$value}}":
|
||||
*e = {{valueName $value}}
|
||||
{{- end}}
|
||||
default:
|
||||
return errors.New("jet: Invalid scan value '" + enumValue + "' for {{$enumTemplate.TypeName}} enum")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e {{$enumTemplate.TypeName}}) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
`
|
||||
@@ -1,13 +0,0 @@
|
||||
package template
|
||||
|
||||
import "regexp"
|
||||
|
||||
// Returns the provided string as golang comment without ascii control characters
|
||||
func formatGolangComment(comment string) string {
|
||||
if len(comment) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Format as colang comment and remove ascii control characters from string
|
||||
return "// " + regexp.MustCompile(`[[:cntrl:]]+`).ReplaceAllString(comment, "")
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package template
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_formatGolangComment(t *testing.T) {
|
||||
type args struct {
|
||||
comment string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{name: "Empty string", args: args{comment: ""}, want: ""},
|
||||
{name: "Non-empty string", args: args{comment: "This is a comment"}, want: "// This is a comment"},
|
||||
{name: "String with control characters", args: args{comment: "This is a comment with control characters \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f and text after"}, want: "// This is a comment with control characters and text after"},
|
||||
{name: "String with escape characters", args: args{comment: "This is a comment with escape characters \n\r\t and text after"}, want: "// This is a comment with escape characters and text after"},
|
||||
{name: "String with unicode characters", args: args{comment: "This is a comment with unicode characters ₲鬼佬℧⇄↻ and text after"}, want: "// This is a comment with unicode characters ₲鬼佬℧⇄↻ and text after"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := formatGolangComment(tt.args.comment); got != tt.want {
|
||||
t.Errorf("formatGoLangComment() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/go-jet/jet/v2/internal/jet"
|
||||
)
|
||||
|
||||
// Template is generator template used for file generation
|
||||
type Template struct {
|
||||
Dialect jet.Dialect
|
||||
Schema func(schemaMetaData metadata.Schema) Schema
|
||||
}
|
||||
|
||||
// Default is default generator template implementation
|
||||
func Default(dialect jet.Dialect) Template {
|
||||
return Template{
|
||||
Dialect: dialect,
|
||||
Schema: DefaultSchema,
|
||||
}
|
||||
}
|
||||
|
||||
// UseSchema replaces current schema generate function with a new implementation and returns new generator template
|
||||
func (t Template) UseSchema(schemaFunc func(schemaMetaData metadata.Schema) Schema) Template {
|
||||
t.Schema = schemaFunc
|
||||
return t
|
||||
}
|
||||
|
||||
// Schema is schema generator template used to generate schema(model and sql builder) files
|
||||
type Schema struct {
|
||||
Path string
|
||||
Model Model
|
||||
SQLBuilder SQLBuilder
|
||||
}
|
||||
|
||||
// UsePath replaces path and returns new schema template
|
||||
func (s Schema) UsePath(path string) Schema {
|
||||
s.Path = path
|
||||
return s
|
||||
}
|
||||
|
||||
// UseModel returns new schema template with replaced template for model files generation
|
||||
func (s Schema) UseModel(model Model) Schema {
|
||||
s.Model = model
|
||||
return s
|
||||
}
|
||||
|
||||
// UseSQLBuilder returns new schema with replaced template for sql builder files generation
|
||||
func (s Schema) UseSQLBuilder(sqlBuilder SQLBuilder) Schema {
|
||||
s.SQLBuilder = sqlBuilder
|
||||
return s
|
||||
}
|
||||
|
||||
// DefaultSchema returns default schema template implementation
|
||||
func DefaultSchema(schemaMetaData metadata.Schema) Schema {
|
||||
return Schema{
|
||||
Path: schemaMetaData.Name,
|
||||
Model: DefaultModel(),
|
||||
SQLBuilder: DefaultSQLBuilder(),
|
||||
}
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/go-jet/jet/v2/internal/utils/dbidentifier"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgtype"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Model is template for model files generation
|
||||
type Model struct {
|
||||
Skip bool
|
||||
Path string
|
||||
Table func(table metadata.Table) TableModel
|
||||
View func(table metadata.Table) ViewModel
|
||||
Enum func(enum metadata.Enum) EnumModel
|
||||
}
|
||||
|
||||
// PackageName returns package name of model types
|
||||
func (m Model) PackageName() string {
|
||||
return path.Base(m.Path)
|
||||
}
|
||||
|
||||
// UsePath returns new Model template with replaced file path
|
||||
func (m Model) UsePath(path string) Model {
|
||||
m.Path = path
|
||||
return m
|
||||
}
|
||||
|
||||
// UseTable returns new Model template with replaced template for table model files generation
|
||||
func (m Model) UseTable(tableModelFunc func(table metadata.Table) TableModel) Model {
|
||||
m.Table = tableModelFunc
|
||||
return m
|
||||
}
|
||||
|
||||
// UseView returns new Model template with replaced template for view model files generation
|
||||
func (m Model) UseView(tableModelFunc func(table metadata.Table) TableModel) Model {
|
||||
m.View = tableModelFunc
|
||||
return m
|
||||
}
|
||||
|
||||
// UseEnum returns new Model template with replaced template for enum model files generation
|
||||
func (m Model) UseEnum(enumFunc func(enumMetaData metadata.Enum) EnumModel) Model {
|
||||
m.Enum = enumFunc
|
||||
return m
|
||||
}
|
||||
|
||||
// DefaultModel returns default Model template implementation
|
||||
func DefaultModel() Model {
|
||||
return Model{
|
||||
Skip: false,
|
||||
Path: "/model",
|
||||
Table: DefaultTableModel,
|
||||
View: DefaultViewModel,
|
||||
Enum: DefaultEnumModel,
|
||||
}
|
||||
}
|
||||
|
||||
// TableModel is template for table model files generation
|
||||
type TableModel struct {
|
||||
Skip bool
|
||||
FileName string
|
||||
TypeName string
|
||||
Field func(columnMetaData metadata.Column) TableModelField
|
||||
}
|
||||
|
||||
// ViewModel is template for view model files generation
|
||||
type ViewModel = TableModel
|
||||
|
||||
// DefaultViewModel is default view template implementation
|
||||
var DefaultViewModel = DefaultTableModel
|
||||
|
||||
// DefaultTableModel is default table template implementation
|
||||
func DefaultTableModel(tableMetaData metadata.Table) TableModel {
|
||||
return TableModel{
|
||||
FileName: dbidentifier.ToGoFileName(tableMetaData.Name),
|
||||
TypeName: dbidentifier.ToGoIdentifier(tableMetaData.Name),
|
||||
Field: DefaultTableModelField,
|
||||
}
|
||||
}
|
||||
|
||||
// UseFileName returns new TableModel with new file name set
|
||||
func (t TableModel) UseFileName(fileName string) TableModel {
|
||||
t.FileName = fileName
|
||||
return t
|
||||
}
|
||||
|
||||
// UseTypeName returns new TableModel with new type name set
|
||||
func (t TableModel) UseTypeName(typeName string) TableModel {
|
||||
t.TypeName = typeName
|
||||
return t
|
||||
}
|
||||
|
||||
// UseField returns new TableModel with new TableModelField template function
|
||||
func (t TableModel) UseField(structFieldFunc func(columnMetaData metadata.Column) TableModelField) TableModel {
|
||||
t.Field = structFieldFunc
|
||||
return t
|
||||
}
|
||||
|
||||
func getTableModelImports(modelType TableModel, tableMetaData metadata.Table) []string {
|
||||
importPaths := map[string]bool{}
|
||||
for _, columnMetaData := range tableMetaData.Columns {
|
||||
field := modelType.Field(columnMetaData)
|
||||
importPath := field.Type.ImportPath
|
||||
|
||||
if importPath != "" {
|
||||
importPaths[importPath] = true
|
||||
}
|
||||
}
|
||||
|
||||
var ret []string
|
||||
for importPath := range importPaths {
|
||||
ret = append(ret, importPath)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// EnumModel is template for enum model files generation
|
||||
type EnumModel struct {
|
||||
Skip bool
|
||||
FileName string
|
||||
TypeName string
|
||||
ValueName func(value string) string
|
||||
}
|
||||
|
||||
// UseFileName returns new EnumModel with new file name set
|
||||
func (em EnumModel) UseFileName(fileName string) EnumModel {
|
||||
em.FileName = fileName
|
||||
return em
|
||||
}
|
||||
|
||||
// UseTypeName returns new EnumModel with new type name set
|
||||
func (em EnumModel) UseTypeName(typeName string) EnumModel {
|
||||
em.TypeName = typeName
|
||||
return em
|
||||
}
|
||||
|
||||
// DefaultEnumModel returns default implementation for EnumModel
|
||||
func DefaultEnumModel(enumMetaData metadata.Enum) EnumModel {
|
||||
typeName := dbidentifier.ToGoIdentifier(enumMetaData.Name)
|
||||
|
||||
return EnumModel{
|
||||
FileName: dbidentifier.ToGoFileName(enumMetaData.Name),
|
||||
TypeName: typeName,
|
||||
ValueName: func(value string) string {
|
||||
return typeName + "_" + dbidentifier.ToGoIdentifier(value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TableModelField is template for table model field generation
|
||||
type TableModelField struct {
|
||||
Name string
|
||||
Type Type
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// DefaultTableModelField returns default TableModelField implementation
|
||||
func DefaultTableModelField(columnMetaData metadata.Column) TableModelField {
|
||||
var tags []string
|
||||
|
||||
if columnMetaData.IsPrimaryKey {
|
||||
tags = append(tags, `sql:"primary_key"`)
|
||||
}
|
||||
|
||||
return TableModelField{
|
||||
Name: dbidentifier.ToGoIdentifier(columnMetaData.Name),
|
||||
Type: getType(columnMetaData),
|
||||
Tags: tags,
|
||||
}
|
||||
}
|
||||
|
||||
// UseType returns new TypeModelField with a new field type set
|
||||
func (f TableModelField) UseType(t Type) TableModelField {
|
||||
f.Type = t
|
||||
return f
|
||||
}
|
||||
|
||||
// UseName returns new TableModelField implementation with new field name set
|
||||
func (f TableModelField) UseName(name string) TableModelField {
|
||||
f.Name = name
|
||||
return f
|
||||
}
|
||||
|
||||
// UseTags returns new TableModelField implementation with additional tags added.
|
||||
func (f TableModelField) UseTags(tags ...string) TableModelField {
|
||||
f.Tags = append(f.Tags, tags...)
|
||||
return f
|
||||
}
|
||||
|
||||
// TagsString returns tags string representation
|
||||
func (f TableModelField) TagsString() string {
|
||||
if len(f.Tags) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("`%s`", strings.Join(f.Tags, " "))
|
||||
}
|
||||
|
||||
// Type represents type of the struct field
|
||||
type Type struct {
|
||||
ImportPath string
|
||||
Name string
|
||||
}
|
||||
|
||||
// NewType creates new type for dummy object
|
||||
func NewType(dummyObject interface{}) Type {
|
||||
return Type{
|
||||
ImportPath: getImportPath(dummyObject),
|
||||
Name: getTypeName(dummyObject),
|
||||
}
|
||||
}
|
||||
|
||||
func getTypeName(t interface{}) string {
|
||||
typeStr := reflect.TypeOf(t).String()
|
||||
typeStr = strings.Replace(typeStr, "[]uint8", "[]byte", -1)
|
||||
|
||||
return typeStr
|
||||
}
|
||||
|
||||
func getImportPath(dummyData interface{}) string {
|
||||
dataType := reflect.TypeOf(dummyData)
|
||||
if dataType.Kind() == reflect.Ptr {
|
||||
return dataType.Elem().PkgPath()
|
||||
}
|
||||
return dataType.PkgPath()
|
||||
}
|
||||
|
||||
func getType(columnMetadata metadata.Column) Type {
|
||||
userDefinedType := getUserDefinedType(columnMetadata)
|
||||
|
||||
if userDefinedType != "" {
|
||||
if columnMetadata.IsNullable {
|
||||
return Type{Name: "*" + userDefinedType}
|
||||
}
|
||||
return Type{Name: userDefinedType}
|
||||
}
|
||||
|
||||
return NewType(getGoType(columnMetadata))
|
||||
}
|
||||
|
||||
func getUserDefinedType(column metadata.Column) string {
|
||||
switch column.DataType.Kind {
|
||||
case metadata.EnumType:
|
||||
return dbidentifier.ToGoIdentifier(column.DataType.Name)
|
||||
case metadata.UserDefinedType, metadata.ArrayType:
|
||||
return "string"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getGoType(column metadata.Column) interface{} {
|
||||
defaultGoType := toGoType(column)
|
||||
|
||||
if column.IsNullable {
|
||||
return reflect.New(reflect.TypeOf(defaultGoType)).Interface()
|
||||
}
|
||||
|
||||
return defaultGoType
|
||||
}
|
||||
|
||||
// toGoType returns model type for column info.
|
||||
func toGoType(column metadata.Column) interface{} {
|
||||
switch strings.ToLower(column.DataType.Name) {
|
||||
case "user-defined", "enum":
|
||||
return ""
|
||||
case "boolean", "bool":
|
||||
return false
|
||||
case "tinyint":
|
||||
if column.DataType.IsUnsigned {
|
||||
return uint8(0)
|
||||
}
|
||||
return int8(0)
|
||||
case "smallint", "int2",
|
||||
"year":
|
||||
if column.DataType.IsUnsigned {
|
||||
return uint16(0)
|
||||
}
|
||||
return int16(0)
|
||||
case "integer", "int4",
|
||||
"mediumint", "int": //MySQL
|
||||
if column.DataType.IsUnsigned {
|
||||
return uint32(0)
|
||||
}
|
||||
return int32(0)
|
||||
case "bigint", "int8":
|
||||
if column.DataType.IsUnsigned {
|
||||
return uint64(0)
|
||||
}
|
||||
return int64(0)
|
||||
case "date",
|
||||
"timestamp without time zone", "timestamp",
|
||||
"timestamp with time zone", "timestamptz",
|
||||
"time without time zone", "time",
|
||||
"time with time zone", "timetz",
|
||||
"datetime": // MySQL
|
||||
return time.Time{}
|
||||
case "bytea",
|
||||
"binary", "varbinary", "tinyblob", "blob", "mediumblob", "longblob": //MySQL
|
||||
return []byte("")
|
||||
case "text",
|
||||
"character", "bpchar",
|
||||
"character varying", "varchar", "nvarchar",
|
||||
"tsvector", "bit", "bit varying", "varbit",
|
||||
"money", "json", "jsonb",
|
||||
"xml", "point", "interval", "line", "array",
|
||||
"char", "tinytext", "mediumtext", "longtext": // MySQL
|
||||
return ""
|
||||
case "real", "float4":
|
||||
return float32(0.0)
|
||||
case "numeric", "decimal",
|
||||
"double precision", "float8", "float",
|
||||
"double": // MySQL
|
||||
return float64(0.0)
|
||||
case "uuid":
|
||||
return uuid.UUID{}
|
||||
case "daterange":
|
||||
return pgtype.Daterange{}
|
||||
case "tsrange":
|
||||
return pgtype.Tsrange{}
|
||||
case "tstzrange":
|
||||
return pgtype.Tstzrange{}
|
||||
case "int4range":
|
||||
return pgtype.Int4range{}
|
||||
case "int8range":
|
||||
return pgtype.Int8range{}
|
||||
case "numrange":
|
||||
return pgtype.Numrange{}
|
||||
default:
|
||||
fmt.Println("- [Model ] Unsupported sql column '" + column.Name + " " + column.DataType.Name + "', using string instead.")
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_TableModelField(t *testing.T) {
|
||||
require.Equal(t, DefaultTableModelField(metadata.Column{
|
||||
Name: "col_name",
|
||||
IsPrimaryKey: true,
|
||||
IsNullable: true,
|
||||
DataType: metadata.DataType{
|
||||
Name: "smallint",
|
||||
Kind: "base",
|
||||
IsUnsigned: true,
|
||||
},
|
||||
}), TableModelField{
|
||||
Name: "ColName",
|
||||
Type: Type{
|
||||
ImportPath: "",
|
||||
Name: "*uint16",
|
||||
},
|
||||
Tags: []string{"sql:\"primary_key\""},
|
||||
})
|
||||
|
||||
require.Equal(t, DefaultTableModelField(metadata.Column{
|
||||
Name: "time_column_1",
|
||||
IsPrimaryKey: false,
|
||||
IsNullable: true,
|
||||
DataType: metadata.DataType{
|
||||
Name: "timestamp with time zone",
|
||||
Kind: "base",
|
||||
IsUnsigned: false,
|
||||
},
|
||||
}), TableModelField{
|
||||
Name: "TimeColumn1",
|
||||
Type: Type{
|
||||
ImportPath: "time",
|
||||
Name: "*time.Time",
|
||||
},
|
||||
Tags: nil,
|
||||
})
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-jet/jet/v2/internal/utils/filesys"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/go-jet/jet/v2/internal/jet"
|
||||
)
|
||||
|
||||
// ProcessSchema will process schema metadata and constructs go files using generator Template
|
||||
func ProcessSchema(dirPath string, schemaMetaData metadata.Schema, generatorTemplate Template) error {
|
||||
if schemaMetaData.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
schemaTemplate := generatorTemplate.Schema(schemaMetaData)
|
||||
schemaPath := path.Join(dirPath, schemaTemplate.Path)
|
||||
|
||||
fmt.Println("Destination directory:", schemaPath)
|
||||
fmt.Println("Cleaning up destination directory...")
|
||||
err := filesys.RemoveDir(schemaPath)
|
||||
if err != nil {
|
||||
return errors.New("failed to cleanup generated files")
|
||||
}
|
||||
|
||||
err = processModel(schemaPath, schemaMetaData, schemaTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate model types: %w", err)
|
||||
}
|
||||
|
||||
err = processSQLBuilder(schemaPath, generatorTemplate.Dialect, schemaMetaData, schemaTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate sql builder types: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processModel(dirPath string, schemaMetaData metadata.Schema, schemaTemplate Schema) error {
|
||||
modelTemplate := schemaTemplate.Model
|
||||
|
||||
if modelTemplate.Skip {
|
||||
fmt.Println("Skipping the generation of model types.")
|
||||
return nil
|
||||
}
|
||||
|
||||
modelDirPath := path.Join(dirPath, modelTemplate.Path)
|
||||
|
||||
err := filesys.EnsureDirPathExist(modelDirPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("destination dir path does not exist: %w", err)
|
||||
}
|
||||
|
||||
err = processTableModels("table", modelDirPath, schemaMetaData.TablesMetaData, modelTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate table model types: %w", err)
|
||||
}
|
||||
|
||||
err = processTableModels("view", modelDirPath, schemaMetaData.ViewsMetaData, modelTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate view model types: %w", err)
|
||||
}
|
||||
|
||||
err = processEnumModels(modelDirPath, schemaMetaData.EnumsMetaData, modelTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process enum types: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processSQLBuilder(dirPath string, dialect jet.Dialect, schemaMetaData metadata.Schema, schemaTemplate Schema) error {
|
||||
sqlBuilderTemplate := schemaTemplate.SQLBuilder
|
||||
|
||||
if sqlBuilderTemplate.Skip {
|
||||
fmt.Println("Skipping the generation of SQL Builder types.")
|
||||
return nil
|
||||
}
|
||||
|
||||
sqlBuilderPath := path.Join(dirPath, sqlBuilderTemplate.Path)
|
||||
|
||||
err := processTableSQLBuilder("table", sqlBuilderPath, dialect, schemaMetaData, schemaMetaData.TablesMetaData, sqlBuilderTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process table sql builder types: %w", err)
|
||||
}
|
||||
|
||||
err = processTableSQLBuilder("view", sqlBuilderPath, dialect, schemaMetaData, schemaMetaData.ViewsMetaData, sqlBuilderTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process view sql builder types: %w", err)
|
||||
}
|
||||
|
||||
err = processEnumSQLBuilder(sqlBuilderPath, dialect, schemaMetaData.EnumsMetaData, sqlBuilderTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process enum types: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processEnumSQLBuilder(dirPath string, dialect jet.Dialect, enumsMetaData []metadata.Enum, sqlBuilder SQLBuilder) error {
|
||||
if len(enumsMetaData) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Generating enum sql builder files\n")
|
||||
|
||||
for _, enumMetaData := range enumsMetaData {
|
||||
enumTemplate := sqlBuilder.Enum(enumMetaData)
|
||||
|
||||
if enumTemplate.Skip {
|
||||
continue
|
||||
}
|
||||
|
||||
enumSQLBuilderPath := path.Join(dirPath, enumTemplate.Path)
|
||||
|
||||
err := filesys.EnsureDirPathExist(enumSQLBuilderPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create enum sql builder directory - %s: %w", enumSQLBuilderPath, err)
|
||||
}
|
||||
|
||||
text, err := generateTemplate(
|
||||
autoGenWarningTemplate+enumSQLBuilderTemplate,
|
||||
enumMetaData,
|
||||
template.FuncMap{
|
||||
"package": func() string {
|
||||
return enumTemplate.PackageName()
|
||||
},
|
||||
"dialect": func() jet.Dialect {
|
||||
return dialect
|
||||
},
|
||||
"enumTemplate": func() EnumSQLBuilder {
|
||||
return enumTemplate
|
||||
},
|
||||
"enumValueName": func(enumValue string) string {
|
||||
return enumTemplate.ValueName(enumValue)
|
||||
},
|
||||
"golangComment": formatGolangComment,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generete enum type %s: %w", enumTemplate.FileName, err)
|
||||
}
|
||||
|
||||
err = filesys.FormatAndSaveGoFile(enumSQLBuilderPath, enumTemplate.FileName, text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to format and save '%s' enum type : %w", enumTemplate.FileName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processTableSQLBuilder(fileTypes, dirPath string,
|
||||
dialect jet.Dialect,
|
||||
schemaMetaData metadata.Schema,
|
||||
tablesMetaData []metadata.Table,
|
||||
sqlBuilderTemplate SQLBuilder) error {
|
||||
|
||||
if len(tablesMetaData) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Generating %s sql builder files\n", fileTypes)
|
||||
|
||||
var generatedBuilders []TableSQLBuilder
|
||||
|
||||
for _, tableMetaData := range tablesMetaData {
|
||||
var tableSQLBuilder TableSQLBuilder
|
||||
|
||||
if fileTypes == "view" {
|
||||
tableSQLBuilder = sqlBuilderTemplate.View(tableMetaData)
|
||||
} else {
|
||||
tableSQLBuilder = sqlBuilderTemplate.Table(tableMetaData)
|
||||
}
|
||||
|
||||
if tableSQLBuilder.Skip {
|
||||
continue
|
||||
}
|
||||
|
||||
tableSQLBuilderPath := path.Join(dirPath, tableSQLBuilder.Path)
|
||||
|
||||
err := filesys.EnsureDirPathExist(tableSQLBuilderPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create table sql builder directory - %s: %w", tableSQLBuilderPath, err)
|
||||
}
|
||||
|
||||
text, err := generateTemplate(
|
||||
autoGenWarningTemplate+tableSQLBuilderTemplate,
|
||||
tableMetaData,
|
||||
template.FuncMap{
|
||||
"package": func() string {
|
||||
return tableSQLBuilder.PackageName()
|
||||
},
|
||||
"dialect": func() jet.Dialect {
|
||||
return dialect
|
||||
},
|
||||
"schemaName": func() string {
|
||||
return schemaMetaData.Name
|
||||
},
|
||||
"tableTemplate": func() TableSQLBuilder {
|
||||
return tableSQLBuilder
|
||||
},
|
||||
"structImplName": func() string { // postgres only
|
||||
structName := tableSQLBuilder.TypeName
|
||||
return string(strings.ToLower(structName)[0]) + structName[1:]
|
||||
},
|
||||
"columnField": func(columnMetaData metadata.Column) TableSQLBuilderColumn {
|
||||
return tableSQLBuilder.Column(columnMetaData)
|
||||
},
|
||||
"toUpper": strings.ToUpper,
|
||||
"insertedRowAlias": func() string {
|
||||
return insertedRowAlias(dialect)
|
||||
},
|
||||
"golangComment": formatGolangComment,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate table sql builder type %s: %w", tableSQLBuilder.TypeName, err)
|
||||
}
|
||||
|
||||
err = filesys.FormatAndSaveGoFile(tableSQLBuilderPath, tableSQLBuilder.FileName, text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to format and save generated sql builder type '%s': %w", tableSQLBuilder.FileName, err)
|
||||
}
|
||||
|
||||
generatedBuilders = append(generatedBuilders, tableSQLBuilder)
|
||||
}
|
||||
|
||||
err := generateUseSchemaFunc(dirPath, fileTypes, generatedBuilders)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate UseSchema function")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateUseSchemaFunc(dirPath, fileTypes string, builders []TableSQLBuilder) error {
|
||||
if len(builders) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
text, err := generateTemplate(
|
||||
autoGenWarningTemplate+tableSqlBuilderSetSchemaTemplate,
|
||||
builders,
|
||||
template.FuncMap{
|
||||
"package": func() string { return builders[0].PackageName() },
|
||||
"type": func() string { return fileTypes },
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate use schema template: %w", err)
|
||||
}
|
||||
|
||||
basePath := path.Join(dirPath, builders[0].Path)
|
||||
fileName := fileTypes + "_use_schema"
|
||||
|
||||
err = filesys.FormatAndSaveGoFile(basePath, fileName, text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save %s file: %w", fileName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertedRowAlias(dialect jet.Dialect) string {
|
||||
if dialect.Name() == "MySQL" {
|
||||
return "new"
|
||||
}
|
||||
|
||||
return "excluded"
|
||||
}
|
||||
|
||||
func processTableModels(fileTypes, modelDirPath string, tablesMetaData []metadata.Table, modelTemplate Model) error {
|
||||
if len(tablesMetaData) == 0 {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("Generating %s model files...\n", fileTypes)
|
||||
|
||||
for _, tableMetaData := range tablesMetaData {
|
||||
var tableTemplate TableModel
|
||||
|
||||
if fileTypes == "table" {
|
||||
tableTemplate = modelTemplate.Table(tableMetaData)
|
||||
} else {
|
||||
tableTemplate = modelTemplate.View(tableMetaData)
|
||||
}
|
||||
|
||||
if tableTemplate.Skip {
|
||||
continue
|
||||
}
|
||||
|
||||
text, err := generateTemplate(
|
||||
autoGenWarningTemplate+tableModelFileTemplate,
|
||||
tableMetaData,
|
||||
template.FuncMap{
|
||||
"package": func() string {
|
||||
return modelTemplate.PackageName()
|
||||
},
|
||||
"modelImports": func() []string {
|
||||
return getTableModelImports(tableTemplate, tableMetaData)
|
||||
},
|
||||
"tableTemplate": func() TableModel {
|
||||
return tableTemplate
|
||||
},
|
||||
"structField": func(columnMetaData metadata.Column) TableModelField {
|
||||
return tableTemplate.Field(columnMetaData)
|
||||
},
|
||||
"golangComment": formatGolangComment,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate model type '%s': %w", tableMetaData.Name, err)
|
||||
}
|
||||
|
||||
err = filesys.FormatAndSaveGoFile(modelDirPath, tableTemplate.FileName, text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save '%s' model type: %w", tableTemplate.FileName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processEnumModels(modelDir string, enumsMetaData []metadata.Enum, modelTemplate Model) error {
|
||||
if len(enumsMetaData) == 0 {
|
||||
return nil
|
||||
}
|
||||
fmt.Print("Generating enum model files...\n")
|
||||
|
||||
for _, enumMetaData := range enumsMetaData {
|
||||
enumTemplate := modelTemplate.Enum(enumMetaData)
|
||||
|
||||
if enumTemplate.Skip {
|
||||
continue
|
||||
}
|
||||
|
||||
text, err := generateTemplate(
|
||||
autoGenWarningTemplate+enumModelTemplate,
|
||||
enumMetaData,
|
||||
template.FuncMap{
|
||||
"package": func() string {
|
||||
return modelTemplate.PackageName()
|
||||
},
|
||||
"enumTemplate": func() EnumModel {
|
||||
return enumTemplate
|
||||
},
|
||||
"valueName": func(value string) string {
|
||||
return enumTemplate.ValueName(value)
|
||||
},
|
||||
"golangComment": formatGolangComment,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate enum type '%s': %w", enumMetaData.Name, err)
|
||||
}
|
||||
|
||||
err = filesys.FormatAndSaveGoFile(modelDir, enumTemplate.FileName, text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save '%s' enum type: %w", enumTemplate.FileName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateTemplate(templateText string, templateData interface{}, funcMap template.FuncMap) ([]byte, error) {
|
||||
t, err := template.New("sqlBuilderTableTemplate").Funcs(funcMap).Parse(templateText)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse template: %w", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := t.Execute(&buf, templateData); err != nil {
|
||||
return nil, fmt.Errorf("failed to generate template: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/go-jet/jet/v2/internal/utils/dbidentifier"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// SQLBuilder is template for generating sql builder files
|
||||
type SQLBuilder struct {
|
||||
Skip bool
|
||||
Path string
|
||||
Table func(table metadata.Table) TableSQLBuilder
|
||||
View func(view metadata.Table) TableSQLBuilder
|
||||
Enum func(enum metadata.Enum) EnumSQLBuilder
|
||||
}
|
||||
|
||||
// DefaultSQLBuilder returns default SQLBuilder implementation
|
||||
func DefaultSQLBuilder() SQLBuilder {
|
||||
return SQLBuilder{
|
||||
Path: "",
|
||||
Table: DefaultTableSQLBuilder,
|
||||
View: DefaultViewSQLBuilder,
|
||||
Enum: DefaultEnumSQLBuilder,
|
||||
}
|
||||
}
|
||||
|
||||
// UsePath returns new SQLBuilder with new relative path set
|
||||
func (sb SQLBuilder) UsePath(path string) SQLBuilder {
|
||||
sb.Path = path
|
||||
return sb
|
||||
}
|
||||
|
||||
// UseTable returns new SQLBuilder with new TableSQLBuilder template function set
|
||||
func (sb SQLBuilder) UseTable(tableFunc func(table metadata.Table) TableSQLBuilder) SQLBuilder {
|
||||
sb.Table = tableFunc
|
||||
return sb
|
||||
}
|
||||
|
||||
// UseView returns new SQLBuilder with new ViewSQLBuilder template function set
|
||||
func (sb SQLBuilder) UseView(viewFunc func(table metadata.Table) ViewSQLBuilder) SQLBuilder {
|
||||
sb.View = viewFunc
|
||||
return sb
|
||||
}
|
||||
|
||||
// UseEnum returns new SQLBuilder with new EnumSQLBuilder template function set
|
||||
func (sb SQLBuilder) UseEnum(enumFunc func(enum metadata.Enum) EnumSQLBuilder) SQLBuilder {
|
||||
sb.Enum = enumFunc
|
||||
return sb
|
||||
}
|
||||
|
||||
// TableSQLBuilder is template for generating table SQLBuilder files
|
||||
type TableSQLBuilder struct {
|
||||
Skip bool
|
||||
Path string
|
||||
FileName string
|
||||
InstanceName string
|
||||
TypeName string
|
||||
DefaultAlias string
|
||||
Column func(columnMetaData metadata.Column) TableSQLBuilderColumn
|
||||
}
|
||||
|
||||
// ViewSQLBuilder is template for generating view SQLBuilder files
|
||||
type ViewSQLBuilder = TableSQLBuilder
|
||||
|
||||
// DefaultTableSQLBuilder returns default implementation for TableSQLBuilder
|
||||
func DefaultTableSQLBuilder(tableMetaData metadata.Table) TableSQLBuilder {
|
||||
tableNameGoIdentifier := dbidentifier.ToGoIdentifier(tableMetaData.Name)
|
||||
|
||||
return TableSQLBuilder{
|
||||
Path: "/table",
|
||||
FileName: dbidentifier.ToGoFileName(tableMetaData.Name),
|
||||
InstanceName: tableNameGoIdentifier,
|
||||
TypeName: tableNameGoIdentifier + "Table",
|
||||
DefaultAlias: "",
|
||||
Column: DefaultTableSQLBuilderColumn,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultViewSQLBuilder returns default implementation for ViewSQLBuilder
|
||||
func DefaultViewSQLBuilder(viewMetaData metadata.Table) ViewSQLBuilder {
|
||||
tableSQLBuilder := DefaultTableSQLBuilder(viewMetaData)
|
||||
tableSQLBuilder.Path = "/view"
|
||||
return tableSQLBuilder
|
||||
}
|
||||
|
||||
// PackageName returns package name of table sql builder types
|
||||
func (tb TableSQLBuilder) PackageName() string {
|
||||
return path.Base(tb.Path)
|
||||
}
|
||||
|
||||
// UsePath returns new TableSQLBuilder with new relative path set
|
||||
func (tb TableSQLBuilder) UsePath(path string) TableSQLBuilder {
|
||||
tb.Path = path
|
||||
return tb
|
||||
}
|
||||
|
||||
// UseFileName returns new TableSQLBuilder with new file name set
|
||||
func (tb TableSQLBuilder) UseFileName(name string) TableSQLBuilder {
|
||||
tb.FileName = name
|
||||
return tb
|
||||
}
|
||||
|
||||
// UseInstanceName returns new TableSQLBuilder with new instance name set
|
||||
func (tb TableSQLBuilder) UseInstanceName(name string) TableSQLBuilder {
|
||||
tb.InstanceName = name
|
||||
return tb
|
||||
}
|
||||
|
||||
// UseTypeName returns new TableSQLBuilder with new type name set
|
||||
func (tb TableSQLBuilder) UseTypeName(name string) TableSQLBuilder {
|
||||
tb.TypeName = name
|
||||
return tb
|
||||
}
|
||||
|
||||
// UseDefaultAlias returns new TableSQLBuilder with new default alias set
|
||||
func (tb TableSQLBuilder) UseDefaultAlias(defaultAlias string) TableSQLBuilder {
|
||||
tb.DefaultAlias = defaultAlias
|
||||
return tb
|
||||
}
|
||||
|
||||
// UseColumn returns new TableSQLBuilder with new column template function set
|
||||
func (tb TableSQLBuilder) UseColumn(columnsFunc func(column metadata.Column) TableSQLBuilderColumn) TableSQLBuilder {
|
||||
tb.Column = columnsFunc
|
||||
return tb
|
||||
}
|
||||
|
||||
// TableSQLBuilderColumn is template for table sql builder column
|
||||
type TableSQLBuilderColumn struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
||||
var reservedKeywords = []string{"TableName", "Table", "SchemaName", "Alias", "AllColumns", "MutableColumns"}
|
||||
|
||||
func renameIfReserved(name string) string {
|
||||
if slices.Contains(reservedKeywords, name) {
|
||||
return name + "_"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// DefaultTableSQLBuilderColumn returns default implementation of TableSQLBuilderColumn
|
||||
func DefaultTableSQLBuilderColumn(columnMetaData metadata.Column) TableSQLBuilderColumn {
|
||||
return TableSQLBuilderColumn{
|
||||
Name: renameIfReserved(dbidentifier.ToGoIdentifier(columnMetaData.Name)),
|
||||
Type: getSqlBuilderColumnType(columnMetaData),
|
||||
}
|
||||
}
|
||||
|
||||
// getSqlBuilderColumnType returns type of jet sql builder column
|
||||
func getSqlBuilderColumnType(columnMetaData metadata.Column) string {
|
||||
if columnMetaData.DataType.Kind != metadata.BaseType &&
|
||||
columnMetaData.DataType.Kind != metadata.RangeType {
|
||||
return "String"
|
||||
}
|
||||
|
||||
switch strings.ToLower(columnMetaData.DataType.Name) {
|
||||
case "boolean", "bool":
|
||||
return "Bool"
|
||||
case "smallint", "integer", "bigint", "int2", "int4", "int8",
|
||||
"tinyint", "mediumint", "int", "year": //MySQL
|
||||
return "Integer"
|
||||
case "date":
|
||||
return "Date"
|
||||
case "timestamp without time zone",
|
||||
"timestamp", "datetime": //MySQL:
|
||||
return "Timestamp"
|
||||
case "timestamp with time zone", "timestamptz":
|
||||
return "Timestampz"
|
||||
case "time without time zone",
|
||||
"time": //MySQL
|
||||
return "Time"
|
||||
case "time with time zone", "timetz":
|
||||
return "Timez"
|
||||
case "interval":
|
||||
return "Interval"
|
||||
case "user-defined", "enum", "text", "character", "character varying", "bytea", "uuid",
|
||||
"tsvector", "bit", "bit varying", "money", "json", "jsonb", "xml", "point", "line", "ARRAY",
|
||||
"char", "varchar", "nvarchar", "binary", "varbinary", "bpchar", "varbit",
|
||||
"tinyblob", "blob", "mediumblob", "longblob", "tinytext", "mediumtext", "longtext": // MySQL
|
||||
return "String"
|
||||
case "real", "numeric", "decimal", "double precision", "float", "float4", "float8",
|
||||
"double": // MySQL
|
||||
return "Float"
|
||||
case "daterange":
|
||||
return "DateRange"
|
||||
case "tsrange":
|
||||
return "TimestampRange"
|
||||
case "tstzrange":
|
||||
return "TimestampzRange"
|
||||
case "int4range":
|
||||
return "Int4Range"
|
||||
case "int8range":
|
||||
return "Int8Range"
|
||||
case "numrange":
|
||||
return "NumericRange"
|
||||
default:
|
||||
fmt.Println("- [SQL Builder] Unsupported sql column '" + columnMetaData.Name + " " + columnMetaData.DataType.Name + "', using StringColumn instead.")
|
||||
return "String"
|
||||
}
|
||||
}
|
||||
|
||||
// EnumSQLBuilder is template for generating enum SQLBuilder files
|
||||
type EnumSQLBuilder struct {
|
||||
Skip bool
|
||||
Path string
|
||||
FileName string
|
||||
InstanceName string
|
||||
ValueName func(enumValue string) string
|
||||
}
|
||||
|
||||
// DefaultEnumSQLBuilder returns default implementation of EnumSQLBuilder
|
||||
func DefaultEnumSQLBuilder(enumMetaData metadata.Enum) EnumSQLBuilder {
|
||||
return EnumSQLBuilder{
|
||||
Path: "/enum",
|
||||
FileName: dbidentifier.ToGoFileName(enumMetaData.Name),
|
||||
InstanceName: dbidentifier.ToGoIdentifier(enumMetaData.Name),
|
||||
ValueName: func(enumValue string) string {
|
||||
return defaultEnumValueName(enumMetaData.Name, enumValue)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// PackageName returns enum sql builder package name
|
||||
func (e EnumSQLBuilder) PackageName() string {
|
||||
return path.Base(e.Path)
|
||||
}
|
||||
|
||||
// UsePath returns new EnumSQLBuilder with new path set
|
||||
func (e EnumSQLBuilder) UsePath(path string) EnumSQLBuilder {
|
||||
e.Path = path
|
||||
return e
|
||||
}
|
||||
|
||||
// UseFileName returns new EnumSQLBuilder with new file name set
|
||||
func (e EnumSQLBuilder) UseFileName(name string) EnumSQLBuilder {
|
||||
e.FileName = name
|
||||
return e
|
||||
}
|
||||
|
||||
// UseInstanceName returns new EnumSQLBuilder with instance name set
|
||||
func (e EnumSQLBuilder) UseInstanceName(name string) EnumSQLBuilder {
|
||||
e.InstanceName = name
|
||||
return e
|
||||
}
|
||||
|
||||
func defaultEnumValueName(enumName, enumValue string) string {
|
||||
enumValueName := dbidentifier.ToGoIdentifier(enumValue)
|
||||
if !unicode.IsLetter([]rune(enumValueName)[0]) {
|
||||
return dbidentifier.ToGoIdentifier(enumName) + enumValueName
|
||||
}
|
||||
|
||||
return enumValueName
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"github.com/go-jet/jet/v2/generator/metadata"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestToGoEnumValueIdentifier(t *testing.T) {
|
||||
require.Equal(t, defaultEnumValueName("enum_name", "enum_value"), "EnumValue")
|
||||
require.Equal(t, defaultEnumValueName("NumEnum", "100"), "NumEnum100")
|
||||
}
|
||||
|
||||
func TestColumnRenameReserved(t *testing.T) {
|
||||
tests := []struct {
|
||||
col string
|
||||
want string
|
||||
}{
|
||||
{col: "TableName", want: "TableName_"},
|
||||
{col: "Table", want: "Table_"},
|
||||
{col: "SchemaName", want: "SchemaName_"},
|
||||
{col: "Alias", want: "Alias_"},
|
||||
{col: "AllColumns", want: "AllColumns_"},
|
||||
{col: "MutableColumns", want: "MutableColumns_"},
|
||||
{col: "OtherColumn", want: "OtherColumn"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.col, func(t *testing.T) {
|
||||
builder := DefaultTableSQLBuilderColumn(metadata.Column{
|
||||
Name: tt.col,
|
||||
})
|
||||
require.Equal(t, builder.Name, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
module github.com/go-jet/jet/v2
|
||||
|
||||
go 1.21
|
||||
|
||||
// used by jet generator
|
||||
require (
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgconn v1.14.3
|
||||
github.com/jackc/pgtype v1.14.4
|
||||
github.com/jackc/pgx/v4 v4.18.3
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-sqlite3 v1.14.24
|
||||
)
|
||||
|
||||
// used in tests
|
||||
require (
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/pkg/profile v1.7.0
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/volatiletech/null/v8 v8.1.2
|
||||
gopkg.in/guregu/null.v4 v4.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/friendsofgo/errors v0.9.2 // indirect
|
||||
github.com/gofrs/uuid v4.0.0+incompatible // indirect
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/volatiletech/inflect v0.0.1 // indirect
|
||||
github.com/volatiletech/randomize v0.0.1 // indirect
|
||||
github.com/volatiletech/strmangle v0.0.1 // indirect
|
||||
golang.org/x/crypto v0.20.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
@@ -1,254 +0,0 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/friendsofgo/errors v0.9.2 h1:X6NYxef4efCBdwI7BgS820zFaN7Cphrmb+Pljdzjtgk=
|
||||
github.com/friendsofgo/errors v0.9.2/go.mod h1:yCvFW5AkDIL9qn7suHVLiI/gH228n7PC4Pn44IGoTOI=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
|
||||
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8=
|
||||
github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
|
||||
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
|
||||
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/volatiletech/inflect v0.0.1 h1:2a6FcMQyhmPZcLa+uet3VJ8gLn/9svWhJxJYwvE8KsU=
|
||||
github.com/volatiletech/inflect v0.0.1/go.mod h1:IBti31tG6phkHitLlr5j7shC5SOo//x0AjDzaJU1PLA=
|
||||
github.com/volatiletech/null/v8 v8.1.2 h1:kiTiX1PpwvuugKwfvUNX/SU/5A2KGZMXfGD0DUHdKEI=
|
||||
github.com/volatiletech/null/v8 v8.1.2/go.mod h1:98DbwNoKEpRrYtGjWFctievIfm4n4MxG0A6EBUcoS5g=
|
||||
github.com/volatiletech/randomize v0.0.1 h1:eE5yajattWqTB2/eN8df4dw+8jwAzBtbdo5sbWC4nMk=
|
||||
github.com/volatiletech/randomize v0.0.1/go.mod h1:GN3U0QYqfZ9FOJ67bzax1cqZ5q2xuj2mXrXBjWaRTlY=
|
||||
github.com/volatiletech/strmangle v0.0.1 h1:UKQoHmY6be/R3tSvD2nQYrH41k43OJkidwEiC74KIzk=
|
||||
github.com/volatiletech/strmangle v0.0.1/go.mod h1:F6RA6IkB5vq0yTG4GQ0UsbbRcl3ni9P76i+JrTBKFFg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
|
||||
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/guregu/null.v4 v4.0.0 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg=
|
||||
gopkg.in/guregu/null.v4 v4.0.0/go.mod h1:YoQhUrADuG3i9WqesrCmpNRwm1ypAgSHYqoOcTu/JrI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
@@ -1,42 +0,0 @@
|
||||
package pq
|
||||
|
||||
// Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FormatTimestamp formats t into Postgres' text format for timestamps. From: github.com/lib/pq
|
||||
func FormatTimestamp(t time.Time) []byte {
|
||||
// Need to send dates before 0001 A.D. with " BC" suffix, instead of the
|
||||
// minus sign preferred by Go.
|
||||
// Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on
|
||||
bc := false
|
||||
if t.Year() <= 0 {
|
||||
// flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11"
|
||||
t = t.AddDate((-t.Year())*2+1, 0, 0)
|
||||
bc = true
|
||||
}
|
||||
b := []byte(t.Format("2006-01-02 15:04:05.999999999Z07:00"))
|
||||
|
||||
_, offset := t.Zone()
|
||||
offset = offset % 60
|
||||
if offset != 0 {
|
||||
// RFC3339Nano already printed the minus sign
|
||||
if offset < 0 {
|
||||
offset = -offset
|
||||
}
|
||||
|
||||
b = append(b, ':')
|
||||
if offset < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
b = strconv.AppendInt(b, int64(offset), 10)
|
||||
}
|
||||
|
||||
if bc {
|
||||
b = append(b, " BC"...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package pq
|
||||
|
||||
// Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var formatTimeTests = []struct {
|
||||
time time.Time
|
||||
expected string
|
||||
}{
|
||||
{time.Time{}, "0001-01-01 00:00:00Z"},
|
||||
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03 04:05:06.123456789Z"},
|
||||
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03 04:05:06.123456789+02:00"},
|
||||
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03 04:05:06.123456789-06:00"},
|
||||
{time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03 04:05:06-07:30:09"},
|
||||
|
||||
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z"},
|
||||
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00"},
|
||||
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00"},
|
||||
|
||||
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z BC"},
|
||||
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00 BC"},
|
||||
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00 BC"},
|
||||
|
||||
{time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09"},
|
||||
{time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09 BC"},
|
||||
}
|
||||
|
||||
func TestFormatTs(t *testing.T) {
|
||||
for i, tt := range formatTimeTests {
|
||||
val := string(FormatTimestamp(tt.time))
|
||||
if val != tt.expected {
|
||||
t.Errorf("%d: incorrect time format %q, want %q", i, val, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
119
tools/jet-2.12.0/internal/3rdparty/snaker/snaker.go
vendored
119
tools/jet-2.12.0/internal/3rdparty/snaker/snaker.go
vendored
@@ -1,119 +0,0 @@
|
||||
package snaker
|
||||
|
||||
// Package snaker provides methods to convert CamelCase names to snake_case and back.
|
||||
// It considers the list of allowed initialsms used by github.com/golang/lint/golint (e.g. ID or HTTP)
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// SnakeToCamel returns a string converted from snake case to uppercase
|
||||
func SnakeToCamel(s string, firstLetterUppercase ...bool) string {
|
||||
upperCase := true
|
||||
if len(firstLetterUppercase) > 0 {
|
||||
upperCase = firstLetterUppercase[0]
|
||||
}
|
||||
return snakeToCamel(s, upperCase)
|
||||
}
|
||||
|
||||
func snakeToCamel(s string, upperCase bool) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
var result string
|
||||
|
||||
words := strings.Split(s, "_")
|
||||
|
||||
for i, word := range words {
|
||||
if exception := snakeToCamelExceptions[word]; len(exception) > 0 {
|
||||
result += exception
|
||||
continue
|
||||
}
|
||||
|
||||
if upperCase || i > 0 {
|
||||
if upper := strings.ToUpper(word); commonInitialisms[upper] {
|
||||
result += upper
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if upperCase || i > 0 {
|
||||
result += camelizeWord(word, len(words) > 1)
|
||||
} else {
|
||||
result += word
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func camelizeWord(word string, force bool) string {
|
||||
runes := []rune(word)
|
||||
|
||||
for i, r := range runes {
|
||||
if i == 0 {
|
||||
runes[i] = unicode.ToUpper(r)
|
||||
} else {
|
||||
if !force && unicode.IsLower(r) { // already camelCase
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
runes[i] = unicode.ToLower(r)
|
||||
}
|
||||
}
|
||||
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// commonInitialisms, taken from
|
||||
// https://github.com/golang/lint/blob/206c0f020eba0f7fbcfbc467a5eb808037df2ed6/lint.go#L731
|
||||
var commonInitialisms = map[string]bool{
|
||||
"ACL": true,
|
||||
"API": true,
|
||||
"ASCII": true,
|
||||
"CPU": true,
|
||||
"CSS": true,
|
||||
"DNS": true,
|
||||
"EOF": true,
|
||||
"ETA": true,
|
||||
"GPU": true,
|
||||
"GUID": true,
|
||||
"HTML": true,
|
||||
"HTTP": true,
|
||||
"HTTPS": true,
|
||||
"ID": true,
|
||||
"IP": true,
|
||||
"JSON": true,
|
||||
"LHS": true,
|
||||
"OS": true,
|
||||
"QPS": true,
|
||||
"RAM": true,
|
||||
"RHS": true,
|
||||
"RPC": true,
|
||||
"SLA": true,
|
||||
"SMTP": true,
|
||||
"SQL": true,
|
||||
"SSH": true,
|
||||
"TCP": true,
|
||||
"TLS": true,
|
||||
"TTL": true,
|
||||
"UDP": true,
|
||||
"UI": true,
|
||||
"UID": true,
|
||||
"UUID": true,
|
||||
"URI": true,
|
||||
"URL": true,
|
||||
"UTF8": true,
|
||||
"VM": true,
|
||||
"XML": true,
|
||||
"XMPP": true,
|
||||
"XSRF": true,
|
||||
"XSS": true,
|
||||
"OAuth": true,
|
||||
}
|
||||
|
||||
// add exceptions here for things that are not automatically convertable
|
||||
var snakeToCamelExceptions = map[string]string{
|
||||
"oauth": "OAuth",
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package snaker
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSnakeToCamel(t *testing.T) {
|
||||
require.Equal(t, SnakeToCamel(""), "")
|
||||
require.Equal(t, SnakeToCamel("potato_"), "Potato")
|
||||
require.Equal(t, SnakeToCamel("this_has_to_be_uppercased"), "ThisHasToBeUppercased")
|
||||
require.Equal(t, SnakeToCamel("this_is_an_id"), "ThisIsAnID")
|
||||
require.Equal(t, SnakeToCamel("this_is_an_identifier"), "ThisIsAnIdentifier")
|
||||
require.Equal(t, SnakeToCamel("id"), "ID")
|
||||
require.Equal(t, SnakeToCamel("oauth_client"), "OAuthClient")
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package jet
|
||||
|
||||
type alias struct {
|
||||
expression Expression
|
||||
alias string
|
||||
}
|
||||
|
||||
func newAlias(expression Expression, aliasName string) Projection {
|
||||
return &alias{
|
||||
expression: expression,
|
||||
alias: aliasName,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *alias) fromImpl(subQuery SelectTable) Projection {
|
||||
// if alias is in the form "table.column", we break it into two parts so that ProjectionList.As(newAlias) can
|
||||
// overwrite tableName with a new alias. This method is called only for exporting aliased custom columns.
|
||||
// Generated columns have default aliasing.
|
||||
tableName, columnName := extractTableAndColumnName(a.alias)
|
||||
|
||||
column := NewColumnImpl(columnName, tableName, nil)
|
||||
column.subQuery = subQuery
|
||||
|
||||
return &column
|
||||
}
|
||||
|
||||
func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||
a.expression.serialize(statement, out)
|
||||
|
||||
out.WriteString("AS")
|
||||
out.WriteAlias(a.alias)
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package jet
|
||||
|
||||
// BoolExpression interface
|
||||
type BoolExpression interface {
|
||||
Expression
|
||||
|
||||
// Check if this expression is equal to rhs
|
||||
EQ(rhs BoolExpression) BoolExpression
|
||||
// Check if this expression is not equal to rhs
|
||||
NOT_EQ(rhs BoolExpression) BoolExpression
|
||||
// Check if this expression is distinct to rhs
|
||||
IS_DISTINCT_FROM(rhs BoolExpression) BoolExpression
|
||||
// Check if this expression is not distinct to rhs
|
||||
IS_NOT_DISTINCT_FROM(rhs BoolExpression) BoolExpression
|
||||
|
||||
// Check if this expression is true
|
||||
IS_TRUE() BoolExpression
|
||||
// Check if this expression is not true
|
||||
IS_NOT_TRUE() BoolExpression
|
||||
// Check if this expression is false
|
||||
IS_FALSE() BoolExpression
|
||||
// Check if this expression is not false
|
||||
IS_NOT_FALSE() BoolExpression
|
||||
// Check if this expression is unknown
|
||||
IS_UNKNOWN() BoolExpression
|
||||
// Check if this expression is not unknown
|
||||
IS_NOT_UNKNOWN() BoolExpression
|
||||
|
||||
// expression AND operator rhs
|
||||
AND(rhs BoolExpression) BoolExpression
|
||||
// expression OR operator rhs
|
||||
OR(rhs BoolExpression) BoolExpression
|
||||
}
|
||||
|
||||
type boolInterfaceImpl struct {
|
||||
parent BoolExpression
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) EQ(expression BoolExpression) BoolExpression {
|
||||
return Eq(b.parent, expression)
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) NOT_EQ(expression BoolExpression) BoolExpression {
|
||||
return NotEq(b.parent, expression)
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_DISTINCT_FROM(rhs BoolExpression) BoolExpression {
|
||||
return IsDistinctFrom(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs BoolExpression) BoolExpression {
|
||||
return IsNotDistinctFrom(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) AND(expression BoolExpression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(b.parent, expression, "AND")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) OR(expression BoolExpression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(b.parent, expression, "OR")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_TRUE() BoolExpression {
|
||||
return newPostfixBoolOperatorExpression(b.parent, "IS TRUE")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_NOT_TRUE() BoolExpression {
|
||||
return newPostfixBoolOperatorExpression(b.parent, "IS NOT TRUE")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_FALSE() BoolExpression {
|
||||
return newPostfixBoolOperatorExpression(b.parent, "IS FALSE")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_NOT_FALSE() BoolExpression {
|
||||
return newPostfixBoolOperatorExpression(b.parent, "IS NOT FALSE")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_UNKNOWN() BoolExpression {
|
||||
return newPostfixBoolOperatorExpression(b.parent, "IS UNKNOWN")
|
||||
}
|
||||
|
||||
func (b *boolInterfaceImpl) IS_NOT_UNKNOWN() BoolExpression {
|
||||
return newPostfixBoolOperatorExpression(b.parent, "IS NOT UNKNOWN")
|
||||
}
|
||||
|
||||
func newBinaryBoolOperatorExpression(lhs, rhs Expression, operator string, additionalParams ...Expression) BoolExpression {
|
||||
return BoolExp(NewBinaryOperatorExpression(lhs, rhs, operator, additionalParams...))
|
||||
}
|
||||
|
||||
func newPrefixBoolOperatorExpression(expression Expression, operator string) BoolExpression {
|
||||
return BoolExp(newPrefixOperatorExpression(expression, operator))
|
||||
}
|
||||
|
||||
func newPostfixBoolOperatorExpression(expression Expression, operator string) BoolExpression {
|
||||
return BoolExp(newPostfixOperatorExpression(expression, operator))
|
||||
}
|
||||
|
||||
type boolExpressionWrapper struct {
|
||||
boolInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newBoolExpressionWrap(expression Expression) BoolExpression {
|
||||
boolExpressionWrap := boolExpressionWrapper{Expression: expression}
|
||||
boolExpressionWrap.boolInterfaceImpl.parent = &boolExpressionWrap
|
||||
return &boolExpressionWrap
|
||||
}
|
||||
|
||||
// BoolExp is bool expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as bool expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func BoolExp(expression Expression) BoolExpression {
|
||||
return newBoolExpressionWrap(expression)
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBoolExpressionEQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.EQ(table2ColBool), "(table1.col_bool = table2.col_bool)")
|
||||
}
|
||||
|
||||
func TestBoolExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.NOT_EQ(table2ColBool), "(table1.col_bool != table2.col_bool)")
|
||||
assertClauseSerialize(t, table1ColBool.NOT_EQ(Bool(true)), "(table1.col_bool != $1)", true)
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(table2ColBool), "(table1.col_bool IS DISTINCT FROM table2.col_bool)")
|
||||
assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS DISTINCT FROM $1)", false)
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(table2ColBool), "(table1.col_bool IS NOT DISTINCT FROM table2.col_bool)")
|
||||
assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS NOT DISTINCT FROM $1)", false)
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_TRUE(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_TRUE(), "table1.col_bool IS TRUE")
|
||||
assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE(),
|
||||
`($1 = table1.col_int) IS TRUE`, int64(2))
|
||||
assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE().AND(Int(4).EQ(table2ColInt)),
|
||||
`(($1 = table1.col_int) IS TRUE AND ($2 = table2.col_int))`, int64(2), int64(4))
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_NOT_TRUE(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_NOT_TRUE(), "table1.col_bool IS NOT TRUE")
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_FALSE(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_FALSE(), "table1.col_bool IS FALSE")
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_NOT_FALSE(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_NOT_FALSE(), "table1.col_bool IS NOT FALSE")
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_UNKNOWN(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_UNKNOWN(), "table1.col_bool IS UNKNOWN")
|
||||
}
|
||||
|
||||
func TestBoolExpressionIS_NOT_UNKNOWN(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColBool.IS_NOT_UNKNOWN(), "table1.col_bool IS NOT UNKNOWN")
|
||||
}
|
||||
|
||||
func TestBinaryBoolExpression(t *testing.T) {
|
||||
boolExpression := Int(2).EQ(Int(3))
|
||||
|
||||
assertClauseSerialize(t, boolExpression, "($1 = $2)", int64(2), int64(3))
|
||||
|
||||
assertProjectionSerialize(t, boolExpression, "$1 = $2", int64(2), int64(3))
|
||||
assertProjectionSerialize(t, boolExpression.AS("alias_eq_expression"),
|
||||
`($1 = $2) AS "alias_eq_expression"`, int64(2), int64(3))
|
||||
assertClauseSerialize(t, boolExpression.AND(Int(4).EQ(Int(5))),
|
||||
"(($1 = $2) AND ($3 = $4))", int64(2), int64(3), int64(4), int64(5))
|
||||
assertClauseSerialize(t, boolExpression.OR(Int(4).EQ(Int(5))),
|
||||
"(($1 = $2) OR ($3 = $4))", int64(2), int64(3), int64(4), int64(5))
|
||||
}
|
||||
|
||||
func TestBoolLiteral(t *testing.T) {
|
||||
assertClauseSerialize(t, Bool(true), "$1", true)
|
||||
assertClauseSerialize(t, Bool(false), "$1", false)
|
||||
}
|
||||
|
||||
func TestBoolExp(t *testing.T) {
|
||||
assertClauseSerialize(t, BoolExp(String("true")), "$1", "true")
|
||||
assertClauseSerialize(t, BoolExp(String("true")).IS_TRUE(), "$1 IS TRUE", "true")
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package jet
|
||||
|
||||
// Cast interface
|
||||
type Cast interface {
|
||||
AS(castType string) Expression
|
||||
}
|
||||
|
||||
type castImpl struct {
|
||||
expression Expression
|
||||
}
|
||||
|
||||
// NewCastImpl creates new generic cast
|
||||
func NewCastImpl(expression Expression) Cast {
|
||||
castImpl := castImpl{
|
||||
expression: expression,
|
||||
}
|
||||
|
||||
return &castImpl
|
||||
}
|
||||
|
||||
func (b *castImpl) AS(castType string) Expression {
|
||||
castExp := &castExpression{
|
||||
expression: b.expression,
|
||||
cast: string(castType),
|
||||
}
|
||||
|
||||
castExp.ExpressionInterfaceImpl.Parent = castExp
|
||||
|
||||
return castExp
|
||||
}
|
||||
|
||||
type castExpression struct {
|
||||
ExpressionInterfaceImpl
|
||||
|
||||
expression Expression
|
||||
cast string
|
||||
}
|
||||
|
||||
func (b *castExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
|
||||
expression := b.expression
|
||||
castType := b.cast
|
||||
|
||||
if castOverride := out.Dialect.OperatorSerializeOverride("CAST"); castOverride != nil {
|
||||
castOverride(expression, String(castType))(statement, out, FallTrough(options)...)
|
||||
return
|
||||
}
|
||||
|
||||
out.WriteString("CAST(")
|
||||
expression.serialize(statement, out, FallTrough(options)...)
|
||||
out.WriteString("AS")
|
||||
out.WriteString(castType + ")")
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCastAS(t *testing.T) {
|
||||
assertClauseSerialize(t, NewCastImpl(Int(1)).AS("boolean"), "CAST($1 AS boolean)", int64(1))
|
||||
assertClauseSerialize(t, NewCastImpl(table2Col3).AS("real"), "CAST(table2.col3 AS real)")
|
||||
assertClauseSerialize(t, NewCastImpl(table2Col3.ADD(table2Col3)).AS("integer"), "CAST((table2.col3 + table2.col3) AS integer)")
|
||||
}
|
||||
@@ -1,672 +0,0 @@
|
||||
package jet
|
||||
|
||||
import "github.com/go-jet/jet/v2/internal/utils/is"
|
||||
|
||||
// Clause interface
|
||||
type Clause interface {
|
||||
Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption)
|
||||
}
|
||||
|
||||
// ClauseWithProjections interface
|
||||
type ClauseWithProjections interface {
|
||||
Clause
|
||||
|
||||
Projections() ProjectionList
|
||||
}
|
||||
|
||||
// OptimizerHint provides a way to optimize query execution per-statement basis
|
||||
type OptimizerHint string
|
||||
|
||||
type optimizerHints []OptimizerHint
|
||||
|
||||
func (o optimizerHints) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(o) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.WriteString("/*+")
|
||||
for i, hint := range o {
|
||||
if i > 0 {
|
||||
out.WriteByte(' ')
|
||||
}
|
||||
|
||||
out.WriteString(string(hint))
|
||||
}
|
||||
out.WriteString("*/")
|
||||
}
|
||||
|
||||
// ClauseSelect struct
|
||||
type ClauseSelect struct {
|
||||
Distinct bool
|
||||
DistinctOnColumns []ColumnExpression
|
||||
ProjectionList []Projection
|
||||
|
||||
// MySQL only
|
||||
OptimizerHints optimizerHints
|
||||
}
|
||||
|
||||
// Projections returns list of projections for select clause
|
||||
func (s *ClauseSelect) Projections() ProjectionList {
|
||||
return s.ProjectionList
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.NewLine()
|
||||
out.WriteString("SELECT")
|
||||
s.OptimizerHints.Serialize(statementType, out, options...)
|
||||
|
||||
if s.Distinct {
|
||||
out.WriteString("DISTINCT")
|
||||
}
|
||||
|
||||
if len(s.DistinctOnColumns) > 0 {
|
||||
out.WriteString("ON (")
|
||||
SerializeColumnExpressions(s.DistinctOnColumns, statementType, out)
|
||||
out.WriteByte(')')
|
||||
}
|
||||
|
||||
if len(s.ProjectionList) == 0 {
|
||||
panic("jet: SELECT clause has to have at least one projection")
|
||||
}
|
||||
|
||||
out.WriteProjections(statementType, s.ProjectionList)
|
||||
}
|
||||
|
||||
// ClauseFrom struct
|
||||
type ClauseFrom struct {
|
||||
Name string
|
||||
Tables []Serializer
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (f *ClauseFrom) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(f.Tables) == 0 { // SELECT statement does not have to have FROM clause
|
||||
return
|
||||
}
|
||||
out.NewLine()
|
||||
if f.Name != "" {
|
||||
out.WriteString(f.Name)
|
||||
} else {
|
||||
out.WriteString("FROM")
|
||||
}
|
||||
|
||||
out.IncreaseIdent()
|
||||
for i, table := range f.Tables {
|
||||
if i > 0 {
|
||||
out.WriteString(",")
|
||||
out.NewLine()
|
||||
}
|
||||
table.serialize(statementType, out, FallTrough(options)...)
|
||||
}
|
||||
out.DecreaseIdent()
|
||||
}
|
||||
|
||||
// ClauseWhere struct
|
||||
type ClauseWhere struct {
|
||||
Condition BoolExpression
|
||||
Mandatory bool
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (c *ClauseWhere) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if c.Condition == nil {
|
||||
if c.Mandatory {
|
||||
panic("jet: WHERE clause not set")
|
||||
}
|
||||
return
|
||||
}
|
||||
if !contains(options, SkipNewLine) {
|
||||
out.NewLine()
|
||||
}
|
||||
out.WriteString("WHERE")
|
||||
|
||||
out.IncreaseIdent(6)
|
||||
c.Condition.serialize(statementType, out, NoWrap.WithFallTrough(options)...)
|
||||
out.DecreaseIdent(6)
|
||||
}
|
||||
|
||||
// ClauseGroupBy struct
|
||||
type ClauseGroupBy struct {
|
||||
List []GroupByClause
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (c *ClauseGroupBy) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(c.List) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.NewLine()
|
||||
out.WriteString("GROUP BY")
|
||||
|
||||
out.IncreaseIdent()
|
||||
|
||||
for i, c := range c.List {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
panic("jet: nil clause in GROUP BY list")
|
||||
}
|
||||
|
||||
c.serializeForGroupBy(statementType, out)
|
||||
}
|
||||
|
||||
out.DecreaseIdent()
|
||||
}
|
||||
|
||||
// ClauseHaving struct
|
||||
type ClauseHaving struct {
|
||||
Condition BoolExpression
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (c *ClauseHaving) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if c.Condition == nil {
|
||||
return
|
||||
}
|
||||
|
||||
out.NewLine()
|
||||
out.WriteString("HAVING")
|
||||
|
||||
out.IncreaseIdent()
|
||||
c.Condition.serialize(statementType, out, NoWrap.WithFallTrough(options)...)
|
||||
out.DecreaseIdent()
|
||||
}
|
||||
|
||||
// ClauseOrderBy struct
|
||||
type ClauseOrderBy struct {
|
||||
List []OrderByClause
|
||||
SkipNewLine bool
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (o *ClauseOrderBy) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if o.List == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !o.SkipNewLine {
|
||||
out.NewLine()
|
||||
}
|
||||
out.WriteString("ORDER BY")
|
||||
|
||||
out.IncreaseIdent()
|
||||
|
||||
for i, value := range o.List {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
|
||||
value.serializeForOrderBy(statementType, out)
|
||||
}
|
||||
|
||||
out.DecreaseIdent()
|
||||
}
|
||||
|
||||
// ClauseLimit struct
|
||||
type ClauseLimit struct {
|
||||
Count int64
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (l *ClauseLimit) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if l.Count >= 0 {
|
||||
out.NewLine()
|
||||
out.WriteString("LIMIT")
|
||||
out.insertParametrizedArgument(l.Count)
|
||||
}
|
||||
}
|
||||
|
||||
// ClauseOffset struct
|
||||
type ClauseOffset struct {
|
||||
Count IntegerExpression
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (o *ClauseOffset) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if is.Nil(o.Count) {
|
||||
return
|
||||
}
|
||||
|
||||
out.NewLine()
|
||||
out.WriteString("OFFSET")
|
||||
o.Count.serialize(statementType, out, options...)
|
||||
}
|
||||
|
||||
// ClauseFetch struct
|
||||
type ClauseFetch struct {
|
||||
Count IntegerExpression
|
||||
WithTies bool
|
||||
}
|
||||
|
||||
// Serialize serializes ClauseFetch into sql builder output
|
||||
func (o *ClauseFetch) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if is.Nil(o.Count) {
|
||||
return
|
||||
}
|
||||
|
||||
out.NewLine()
|
||||
out.WriteString("FETCH FIRST")
|
||||
o.Count.serialize(statementType, out, options...)
|
||||
|
||||
if o.WithTies {
|
||||
out.WriteString("ROWS WITH TIES")
|
||||
} else {
|
||||
out.WriteString("ROWS ONLY")
|
||||
}
|
||||
}
|
||||
|
||||
// ClauseFor struct
|
||||
type ClauseFor struct {
|
||||
Lock RowLock
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (f *ClauseFor) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if f.Lock == nil {
|
||||
return
|
||||
}
|
||||
|
||||
out.NewLine()
|
||||
out.WriteString("FOR")
|
||||
f.Lock.serialize(statementType, out, FallTrough(options)...)
|
||||
}
|
||||
|
||||
// ClauseSetStmtOperator struct
|
||||
type ClauseSetStmtOperator struct {
|
||||
Operator string
|
||||
All bool
|
||||
Selects []SerializerStatement
|
||||
OrderBy ClauseOrderBy
|
||||
Limit ClauseLimit
|
||||
Offset ClauseOffset
|
||||
SkipSelectWrap bool
|
||||
}
|
||||
|
||||
// Projections returns set of projections for ClauseSetStmtOperator
|
||||
func (s *ClauseSetStmtOperator) Projections() ProjectionList {
|
||||
if len(s.Selects) > 0 {
|
||||
return s.Selects[0].projections()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (s *ClauseSetStmtOperator) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(s.Selects) < 2 {
|
||||
panic("jet: UNION Statement must contain at least two SELECT statements")
|
||||
}
|
||||
|
||||
for i, selectStmt := range s.Selects {
|
||||
out.NewLine()
|
||||
if i > 0 {
|
||||
if s.SkipSelectWrap {
|
||||
out.NewLine()
|
||||
}
|
||||
|
||||
out.WriteString(s.Operator)
|
||||
|
||||
if s.All {
|
||||
out.WriteString("ALL")
|
||||
}
|
||||
out.NewLine()
|
||||
}
|
||||
|
||||
if selectStmt == nil {
|
||||
panic("jet: select statement of '" + s.Operator + "' is nil")
|
||||
}
|
||||
|
||||
if s.SkipSelectWrap {
|
||||
options = append(FallTrough(options), NoWrap)
|
||||
}
|
||||
|
||||
selectStmt.serialize(statementType, out, options...)
|
||||
}
|
||||
|
||||
s.OrderBy.Serialize(statementType, out)
|
||||
s.Limit.Serialize(statementType, out)
|
||||
s.Offset.Serialize(statementType, out)
|
||||
}
|
||||
|
||||
// ClauseUpdate struct
|
||||
type ClauseUpdate struct {
|
||||
Table SerializerTable
|
||||
|
||||
// MySQL only
|
||||
OptimizerHints optimizerHints
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (u *ClauseUpdate) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.NewLine()
|
||||
out.WriteString("UPDATE")
|
||||
u.OptimizerHints.Serialize(statementType, out, options...)
|
||||
|
||||
if is.Nil(u.Table) {
|
||||
panic("jet: table to update is nil")
|
||||
}
|
||||
|
||||
u.Table.serialize(statementType, out, FallTrough(options)...)
|
||||
}
|
||||
|
||||
// SetClause struct
|
||||
type SetClause struct {
|
||||
Columns []Column
|
||||
Values []Serializer
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (s *SetClause) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(s.Values) == 0 {
|
||||
return
|
||||
}
|
||||
out.NewLine()
|
||||
out.WriteString("SET")
|
||||
|
||||
if len(s.Columns) != len(s.Values) {
|
||||
panic("jet: mismatch in numbers of columns and values for SET clause")
|
||||
}
|
||||
|
||||
out.IncreaseIdent(4)
|
||||
for i, column := range s.Columns {
|
||||
if i > 0 {
|
||||
out.WriteString(",")
|
||||
out.NewLine()
|
||||
}
|
||||
|
||||
if column == nil {
|
||||
panic("jet: nil column in columns list for SET clause")
|
||||
}
|
||||
|
||||
out.WriteIdentifier(column.Name())
|
||||
|
||||
out.WriteString(" = ")
|
||||
|
||||
s.Values[i].serialize(UpdateStatementType, out, FallTrough(options)...)
|
||||
}
|
||||
out.DecreaseIdent(4)
|
||||
}
|
||||
|
||||
// ClauseInsert struct
|
||||
type ClauseInsert struct {
|
||||
Table SerializerTable
|
||||
Columns []Column
|
||||
|
||||
// MySQL only
|
||||
OptimizerHints optimizerHints
|
||||
}
|
||||
|
||||
// GetColumns gets list of columns for insert
|
||||
func (i *ClauseInsert) GetColumns() []Column {
|
||||
if len(i.Columns) > 0 {
|
||||
return i.Columns
|
||||
}
|
||||
|
||||
return i.Table.columns()
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (i *ClauseInsert) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if is.Nil(i.Table) {
|
||||
panic("jet: table is nil for INSERT clause")
|
||||
}
|
||||
|
||||
out.NewLine()
|
||||
out.WriteString("INSERT")
|
||||
i.OptimizerHints.Serialize(statementType, out, options...)
|
||||
out.WriteString("INTO")
|
||||
|
||||
i.Table.serialize(statementType, out)
|
||||
|
||||
if len(i.Columns) > 0 {
|
||||
out.WriteString("(")
|
||||
|
||||
SerializeColumnNames(i.Columns, out)
|
||||
|
||||
out.WriteString(")")
|
||||
}
|
||||
}
|
||||
|
||||
// ClauseValuesQuery struct
|
||||
type ClauseValuesQuery struct {
|
||||
ClauseValues
|
||||
ClauseQuery
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (v *ClauseValuesQuery) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(v.Rows) > 0 && v.Query != nil {
|
||||
panic("jet: VALUES or QUERY has to be specified for INSERT statement")
|
||||
}
|
||||
|
||||
v.ClauseValues.Serialize(statementType, out, FallTrough(options)...)
|
||||
v.ClauseQuery.Serialize(statementType, out, FallTrough(options)...)
|
||||
}
|
||||
|
||||
// ClauseValues struct
|
||||
type ClauseValues struct {
|
||||
Rows [][]Serializer
|
||||
As string
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (v *ClauseValues) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(v.Rows) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.NewLine()
|
||||
out.WriteString("VALUES")
|
||||
|
||||
for rowIndex, row := range v.Rows {
|
||||
if rowIndex > 0 {
|
||||
out.WriteString(",")
|
||||
out.NewLine()
|
||||
} else {
|
||||
out.IncreaseIdent(7)
|
||||
}
|
||||
|
||||
out.WriteString("(")
|
||||
|
||||
SerializeClauseList(statementType, row, out)
|
||||
|
||||
out.WriteByte(')')
|
||||
}
|
||||
|
||||
if len(v.As) > 0 {
|
||||
out.WriteString("AS")
|
||||
out.WriteIdentifier(v.As)
|
||||
}
|
||||
|
||||
out.DecreaseIdent(7)
|
||||
}
|
||||
|
||||
// ClauseQuery struct
|
||||
type ClauseQuery struct {
|
||||
Query SerializerStatement
|
||||
SkipSelectWrap bool
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (v *ClauseQuery) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if v.Query == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if v.SkipSelectWrap {
|
||||
options = append(FallTrough(options), NoWrap)
|
||||
}
|
||||
|
||||
v.Query.serialize(statementType, out, options...)
|
||||
}
|
||||
|
||||
// ClauseDelete struct
|
||||
type ClauseDelete struct {
|
||||
Table SerializerTable
|
||||
|
||||
// MySQL only
|
||||
OptimizerHints optimizerHints
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (d *ClauseDelete) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.NewLine()
|
||||
out.WriteString("DELETE")
|
||||
d.OptimizerHints.Serialize(statementType, out, options...)
|
||||
out.WriteString("FROM")
|
||||
d.Table.serialize(statementType, out, FallTrough(options)...)
|
||||
}
|
||||
|
||||
// ClauseStatementBegin struct
|
||||
type ClauseStatementBegin struct {
|
||||
Name string
|
||||
Tables []SerializerTable
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (d *ClauseStatementBegin) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.NewLine()
|
||||
out.WriteString(d.Name)
|
||||
|
||||
for i, table := range d.Tables {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
|
||||
table.serialize(statementType, out, FallTrough(options)...)
|
||||
}
|
||||
}
|
||||
|
||||
// ClauseOptional struct
|
||||
type ClauseOptional struct {
|
||||
Name string
|
||||
Show bool
|
||||
InNewLine bool
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (d *ClauseOptional) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if !d.Show {
|
||||
return
|
||||
}
|
||||
if d.InNewLine {
|
||||
out.NewLine()
|
||||
}
|
||||
out.WriteString(d.Name)
|
||||
}
|
||||
|
||||
// ClauseIn struct
|
||||
type ClauseIn struct {
|
||||
LockMode string
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (i *ClauseIn) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if i.LockMode == "" {
|
||||
return
|
||||
}
|
||||
|
||||
out.WriteString("IN")
|
||||
out.WriteString(string(i.LockMode))
|
||||
out.WriteString("MODE")
|
||||
}
|
||||
|
||||
// WindowDefinition struct
|
||||
type WindowDefinition struct {
|
||||
Name string
|
||||
Window Window
|
||||
}
|
||||
|
||||
// ClauseWindow struct
|
||||
type ClauseWindow struct {
|
||||
Definitions []WindowDefinition
|
||||
}
|
||||
|
||||
// Serialize serializes clause into SQLBuilder
|
||||
func (i *ClauseWindow) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(i.Definitions) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.NewLine()
|
||||
out.WriteString("WINDOW")
|
||||
|
||||
for i, def := range i.Definitions {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
out.WriteString(def.Name)
|
||||
out.WriteString("AS")
|
||||
if def.Window == nil {
|
||||
out.WriteString("()")
|
||||
continue
|
||||
}
|
||||
def.Window.serialize(statementType, out, FallTrough(options)...)
|
||||
}
|
||||
}
|
||||
|
||||
// SetPair clause
|
||||
type SetPair struct {
|
||||
Column ColumnSerializer
|
||||
Value Serializer
|
||||
}
|
||||
|
||||
// SetClauseNew clause
|
||||
type SetClauseNew []ColumnAssigment
|
||||
|
||||
// Serialize for SetClauseNew
|
||||
func (s SetClauseNew) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
out.NewLine()
|
||||
out.WriteString("SET")
|
||||
out.IncreaseIdent(4)
|
||||
|
||||
for i, assigment := range s {
|
||||
if i > 0 {
|
||||
out.WriteString(",")
|
||||
out.NewLine()
|
||||
}
|
||||
|
||||
assigment.serialize(statementType, out, FallTrough(options)...)
|
||||
}
|
||||
|
||||
out.DecreaseIdent(4)
|
||||
}
|
||||
|
||||
// KeywordClause type
|
||||
type KeywordClause struct {
|
||||
Keyword
|
||||
}
|
||||
|
||||
// Serialize for KeywordClause
|
||||
func (k KeywordClause) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
k.serialize(statementType, out, FallTrough(options)...)
|
||||
}
|
||||
|
||||
// ClauseReturning type
|
||||
type ClauseReturning struct {
|
||||
ProjectionList []Projection
|
||||
}
|
||||
|
||||
// Serialize for ClauseReturning
|
||||
func (r *ClauseReturning) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(r.ProjectionList) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.NewLine()
|
||||
out.WriteString("RETURNING")
|
||||
out.IncreaseIdent()
|
||||
out.WriteProjections(statementType, r.ProjectionList)
|
||||
out.DecreaseIdent()
|
||||
}
|
||||
|
||||
// Projections for ClauseReturning
|
||||
func (r ClauseReturning) Projections() ProjectionList {
|
||||
return r.ProjectionList
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClauseSelect_Serialize(t *testing.T) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
require.Equal(t, r, "jet: SELECT clause has to have at least one projection")
|
||||
}()
|
||||
|
||||
selectClause := &ClauseSelect{}
|
||||
selectClause.Serialize(SelectStatementType, &SQLBuilder{})
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
// Modeling of columns
|
||||
|
||||
package jet
|
||||
|
||||
// Column is common column interface for all types of columns.
|
||||
type Column interface {
|
||||
Name() string
|
||||
TableName() string
|
||||
|
||||
setTableName(table string)
|
||||
setSubQuery(subQuery SelectTable)
|
||||
defaultAlias() string
|
||||
}
|
||||
|
||||
// ColumnSerializer is interface for all serializable columns
|
||||
type ColumnSerializer interface {
|
||||
Serializer
|
||||
Column
|
||||
}
|
||||
|
||||
// ColumnExpression interface
|
||||
type ColumnExpression interface {
|
||||
Column
|
||||
Expression
|
||||
}
|
||||
|
||||
// ColumnExpressionImpl is base type for sql columns.
|
||||
type ColumnExpressionImpl struct {
|
||||
ExpressionInterfaceImpl
|
||||
|
||||
name string
|
||||
tableName string
|
||||
|
||||
subQuery SelectTable
|
||||
}
|
||||
|
||||
// NewColumnImpl creates new ColumnExpressionImpl
|
||||
func NewColumnImpl(name string, tableName string, parent ColumnExpression) ColumnExpressionImpl {
|
||||
bc := ColumnExpressionImpl{
|
||||
name: name,
|
||||
tableName: tableName,
|
||||
}
|
||||
|
||||
if parent != nil {
|
||||
bc.ExpressionInterfaceImpl.Parent = parent
|
||||
} else {
|
||||
bc.ExpressionInterfaceImpl.Parent = &bc
|
||||
}
|
||||
|
||||
return bc
|
||||
}
|
||||
|
||||
// Name returns name of the column
|
||||
func (c *ColumnExpressionImpl) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
// TableName returns column table name
|
||||
func (c *ColumnExpressionImpl) TableName() string {
|
||||
return c.tableName
|
||||
}
|
||||
|
||||
func (c *ColumnExpressionImpl) setTableName(table string) {
|
||||
c.tableName = table
|
||||
}
|
||||
|
||||
func (c *ColumnExpressionImpl) setSubQuery(subQuery SelectTable) {
|
||||
c.subQuery = subQuery
|
||||
}
|
||||
|
||||
func (c *ColumnExpressionImpl) defaultAlias() string {
|
||||
if c.tableName != "" {
|
||||
return c.tableName + "." + c.name
|
||||
}
|
||||
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *ColumnExpressionImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newColumn := NewColumnImpl(c.name, c.tableName, nil)
|
||||
newColumn.setSubQuery(subQuery)
|
||||
|
||||
return &newColumn
|
||||
}
|
||||
|
||||
func (c *ColumnExpressionImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
|
||||
if statement == SetStatementType {
|
||||
// set Statement (UNION, EXCEPT ...) can reference only select projections in order by clause
|
||||
out.WriteAlias(c.defaultAlias()) //always quote
|
||||
return
|
||||
}
|
||||
|
||||
c.serialize(statement, out)
|
||||
}
|
||||
|
||||
func (c ColumnExpressionImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||
c.serialize(statement, out)
|
||||
|
||||
out.WriteString("AS")
|
||||
out.WriteAlias(c.defaultAlias())
|
||||
}
|
||||
|
||||
func (c ColumnExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
|
||||
if c.subQuery != nil {
|
||||
out.WriteIdentifier(c.subQuery.Alias())
|
||||
out.WriteByte('.')
|
||||
out.WriteIdentifier(c.defaultAlias())
|
||||
} else {
|
||||
if c.tableName != "" && !contains(options, ShortName) {
|
||||
out.WriteIdentifier(c.tableName)
|
||||
out.WriteByte('.')
|
||||
}
|
||||
|
||||
out.WriteIdentifier(c.name)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package jet
|
||||
|
||||
// ColumnAssigment is interface wrapper around column assigment
|
||||
type ColumnAssigment interface {
|
||||
Serializer
|
||||
isColumnAssigment()
|
||||
}
|
||||
|
||||
type columnAssigmentImpl struct {
|
||||
column ColumnSerializer
|
||||
expression Expression
|
||||
}
|
||||
|
||||
func NewColumnAssignment(serializer ColumnSerializer, expression Expression) ColumnAssigment {
|
||||
return &columnAssigmentImpl{
|
||||
column: serializer,
|
||||
expression: expression,
|
||||
}
|
||||
}
|
||||
|
||||
func (a columnAssigmentImpl) isColumnAssigment() {}
|
||||
|
||||
func (a columnAssigmentImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
a.column.serialize(statement, out, ShortName.WithFallTrough(options)...)
|
||||
out.WriteString("=")
|
||||
a.expression.serialize(statement, out, FallTrough(options)...)
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package jet
|
||||
|
||||
// ColumnList is a helper type to support list of columns as single projection
|
||||
type ColumnList []ColumnExpression
|
||||
|
||||
// SET creates column assigment for each column in column list. expression should be created by ROW function
|
||||
//
|
||||
// Link.UPDATE().
|
||||
// SET(Link.MutableColumns.SET(ROW(String("github.com"), Bool(false))).
|
||||
// WHERE(Link.ID.EQ(Int(0)))
|
||||
func (cl ColumnList) SET(expression Expression) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: cl,
|
||||
expression: expression,
|
||||
}
|
||||
}
|
||||
|
||||
// Except will create new column list in which columns contained in list of excluded column names are removed
|
||||
//
|
||||
// Address.AllColumns.Except(Address.PostalCode, Address.Phone)
|
||||
func (cl ColumnList) Except(excludedColumns ...Column) ColumnList {
|
||||
excludedColumnList := UnwidColumnList(excludedColumns)
|
||||
excludedColumnNames := map[string]bool{}
|
||||
|
||||
for _, excludedColumn := range excludedColumnList {
|
||||
excludedColumnNames[excludedColumn.Name()] = true
|
||||
}
|
||||
|
||||
var ret ColumnList
|
||||
|
||||
for _, column := range cl {
|
||||
if excludedColumnNames[column.Name()] {
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, column)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// As will create new projection list where each column is wrapped with a new table alias.
|
||||
// tableAlias should be in the form 'name' or 'name.*', or it can also be an empty string.
|
||||
// For instance: If projection list has a column 'Artist.Name', and tableAlias is 'Musician.*', returned projection list will
|
||||
// have a column wrapped in alias 'Musician.Name'. If tableAlias is empty string, it removes existing table alias ('Artist.Name' becomes 'Name').
|
||||
func (cl ColumnList) As(tableAlias string) ProjectionList {
|
||||
ret := make(ProjectionList, 0, len(cl))
|
||||
for _, c := range cl {
|
||||
ret = append(ret, c.AS(joinAlias(tableAlias, c.Name())))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (cl ColumnList) fromImpl(subQuery SelectTable) Projection {
|
||||
newProjectionList := ProjectionList{}
|
||||
|
||||
for _, column := range cl {
|
||||
newProjectionList = append(newProjectionList, column.fromImpl(subQuery))
|
||||
}
|
||||
|
||||
return newProjectionList
|
||||
}
|
||||
|
||||
func (cl ColumnList) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.WriteString("(")
|
||||
for i, column := range cl {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
column.serialize(statement, out, FallTrough(options)...)
|
||||
}
|
||||
out.WriteString(")")
|
||||
}
|
||||
|
||||
func (cl ColumnList) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||
projections := ColumnListToProjectionList(cl)
|
||||
|
||||
SerializeProjectionList(statement, projections, out)
|
||||
}
|
||||
|
||||
// dummy column interface implementation
|
||||
|
||||
// Name is placeholder for ColumnList to implement Column interface
|
||||
func (cl ColumnList) Name() string { return "" }
|
||||
|
||||
// TableName is placeholder for ColumnList to implement Column interface
|
||||
func (cl ColumnList) TableName() string { return "" }
|
||||
func (cl ColumnList) setTableName(name string) {}
|
||||
func (cl ColumnList) setSubQuery(subQuery SelectTable) {}
|
||||
func (cl ColumnList) defaultAlias() string { return "" }
|
||||
|
||||
// SetTableName is utility function to set table name from outside of jet package to avoid making public setTableName
|
||||
func SetTableName(columnExpression ColumnExpression, tableName string) {
|
||||
columnExpression.setTableName(tableName)
|
||||
}
|
||||
|
||||
// SetSubQuery is utility function to set table name from outside of jet package to avoid making public setSubQuery
|
||||
func SetSubQuery(columnExpression ColumnExpression, subQuery SelectTable) {
|
||||
columnExpression.setSubQuery(subQuery)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package jet
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestColumn(t *testing.T) {
|
||||
column := NewColumnImpl("col", "", nil)
|
||||
column.ExpressionInterfaceImpl.Parent = &column
|
||||
|
||||
assertClauseSerialize(t, column, "col")
|
||||
column.setTableName("table1")
|
||||
assertClauseSerialize(t, column, "table1.col")
|
||||
assertProjectionSerialize(t, &column, `table1.col AS "table1.col"`)
|
||||
assertProjectionSerialize(t, column.AS("alias1"), `table1.col AS "alias1"`)
|
||||
}
|
||||
@@ -1,401 +0,0 @@
|
||||
package jet
|
||||
|
||||
// ColumnBool is interface for SQL boolean columns.
|
||||
type ColumnBool interface {
|
||||
BoolExpression
|
||||
Column
|
||||
|
||||
From(subQuery SelectTable) ColumnBool
|
||||
SET(boolExp BoolExpression) ColumnAssigment
|
||||
}
|
||||
|
||||
type boolColumnImpl struct {
|
||||
boolInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *boolColumnImpl) From(subQuery SelectTable) ColumnBool {
|
||||
newBoolColumn := BoolColumn(i.name)
|
||||
newBoolColumn.setTableName(i.tableName)
|
||||
newBoolColumn.setSubQuery(subQuery)
|
||||
|
||||
return newBoolColumn
|
||||
}
|
||||
|
||||
func (i *boolColumnImpl) SET(boolExp BoolExpression) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: i,
|
||||
expression: boolExp,
|
||||
}
|
||||
}
|
||||
|
||||
// BoolColumn creates named bool column.
|
||||
func BoolColumn(name string) ColumnBool {
|
||||
boolColumn := &boolColumnImpl{}
|
||||
boolColumn.ColumnExpressionImpl = NewColumnImpl(name, "", boolColumn)
|
||||
boolColumn.boolInterfaceImpl.parent = boolColumn
|
||||
|
||||
return boolColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnFloat is interface for SQL real, numeric, decimal or double precision column.
|
||||
type ColumnFloat interface {
|
||||
FloatExpression
|
||||
Column
|
||||
|
||||
From(subQuery SelectTable) ColumnFloat
|
||||
SET(floatExp FloatExpression) ColumnAssigment
|
||||
}
|
||||
|
||||
type floatColumnImpl struct {
|
||||
floatInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *floatColumnImpl) From(subQuery SelectTable) ColumnFloat {
|
||||
newFloatColumn := FloatColumn(i.name)
|
||||
newFloatColumn.setTableName(i.tableName)
|
||||
newFloatColumn.setSubQuery(subQuery)
|
||||
|
||||
return newFloatColumn
|
||||
}
|
||||
|
||||
func (i *floatColumnImpl) SET(floatExp FloatExpression) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: i,
|
||||
expression: floatExp,
|
||||
}
|
||||
}
|
||||
|
||||
// FloatColumn creates named float column.
|
||||
func FloatColumn(name string) ColumnFloat {
|
||||
floatColumn := &floatColumnImpl{}
|
||||
floatColumn.floatInterfaceImpl.parent = floatColumn
|
||||
floatColumn.ColumnExpressionImpl = NewColumnImpl(name, "", floatColumn)
|
||||
|
||||
return floatColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnInteger is interface for SQL smallint, integer, bigint columns.
|
||||
type ColumnInteger interface {
|
||||
IntegerExpression
|
||||
Column
|
||||
|
||||
From(subQuery SelectTable) ColumnInteger
|
||||
SET(intExp IntegerExpression) ColumnAssigment
|
||||
}
|
||||
|
||||
type integerColumnImpl struct {
|
||||
integerInterfaceImpl
|
||||
|
||||
ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *integerColumnImpl) From(subQuery SelectTable) ColumnInteger {
|
||||
newIntColumn := IntegerColumn(i.name)
|
||||
newIntColumn.setTableName(i.tableName)
|
||||
newIntColumn.setSubQuery(subQuery)
|
||||
|
||||
return newIntColumn
|
||||
}
|
||||
|
||||
func (i *integerColumnImpl) SET(intExp IntegerExpression) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: i,
|
||||
expression: intExp,
|
||||
}
|
||||
}
|
||||
|
||||
// IntegerColumn creates named integer column.
|
||||
func IntegerColumn(name string) ColumnInteger {
|
||||
integerColumn := &integerColumnImpl{}
|
||||
integerColumn.integerInterfaceImpl.parent = integerColumn
|
||||
integerColumn.ColumnExpressionImpl = NewColumnImpl(name, "", integerColumn)
|
||||
|
||||
return integerColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnString is interface for SQL text, character, character varying
|
||||
// bytea, uuid columns and enums types.
|
||||
type ColumnString interface {
|
||||
StringExpression
|
||||
Column
|
||||
|
||||
From(subQuery SelectTable) ColumnString
|
||||
SET(stringExp StringExpression) ColumnAssigment
|
||||
}
|
||||
|
||||
type stringColumnImpl struct {
|
||||
stringInterfaceImpl
|
||||
|
||||
ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *stringColumnImpl) From(subQuery SelectTable) ColumnString {
|
||||
newStrColumn := StringColumn(i.name)
|
||||
newStrColumn.setTableName(i.tableName)
|
||||
newStrColumn.setSubQuery(subQuery)
|
||||
|
||||
return newStrColumn
|
||||
}
|
||||
|
||||
func (i *stringColumnImpl) SET(stringExp StringExpression) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: i,
|
||||
expression: stringExp,
|
||||
}
|
||||
}
|
||||
|
||||
// StringColumn creates named string column.
|
||||
func StringColumn(name string) ColumnString {
|
||||
stringColumn := &stringColumnImpl{}
|
||||
stringColumn.stringInterfaceImpl.parent = stringColumn
|
||||
stringColumn.ColumnExpressionImpl = NewColumnImpl(name, "", stringColumn)
|
||||
|
||||
return stringColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnTime is interface for SQL time column.
|
||||
type ColumnTime interface {
|
||||
TimeExpression
|
||||
Column
|
||||
|
||||
From(subQuery SelectTable) ColumnTime
|
||||
SET(timeExp TimeExpression) ColumnAssigment
|
||||
}
|
||||
|
||||
type timeColumnImpl struct {
|
||||
timeInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *timeColumnImpl) From(subQuery SelectTable) ColumnTime {
|
||||
newTimeColumn := TimeColumn(i.name)
|
||||
newTimeColumn.setTableName(i.tableName)
|
||||
newTimeColumn.setSubQuery(subQuery)
|
||||
|
||||
return newTimeColumn
|
||||
}
|
||||
|
||||
func (i *timeColumnImpl) SET(timeExp TimeExpression) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: i,
|
||||
expression: timeExp,
|
||||
}
|
||||
}
|
||||
|
||||
// TimeColumn creates named time column
|
||||
func TimeColumn(name string) ColumnTime {
|
||||
timeColumn := &timeColumnImpl{}
|
||||
timeColumn.timeInterfaceImpl.parent = timeColumn
|
||||
timeColumn.ColumnExpressionImpl = NewColumnImpl(name, "", timeColumn)
|
||||
return timeColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnTimez is interface of SQL time with time zone columns.
|
||||
type ColumnTimez interface {
|
||||
TimezExpression
|
||||
Column
|
||||
|
||||
From(subQuery SelectTable) ColumnTimez
|
||||
SET(timeExp TimezExpression) ColumnAssigment
|
||||
}
|
||||
|
||||
type timezColumnImpl struct {
|
||||
timezInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *timezColumnImpl) From(subQuery SelectTable) ColumnTimez {
|
||||
newTimezColumn := TimezColumn(i.name)
|
||||
newTimezColumn.setTableName(i.tableName)
|
||||
newTimezColumn.setSubQuery(subQuery)
|
||||
|
||||
return newTimezColumn
|
||||
}
|
||||
|
||||
func (i *timezColumnImpl) SET(timezExp TimezExpression) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: i,
|
||||
expression: timezExp,
|
||||
}
|
||||
}
|
||||
|
||||
// TimezColumn creates named time with time zone column.
|
||||
func TimezColumn(name string) ColumnTimez {
|
||||
timezColumn := &timezColumnImpl{}
|
||||
timezColumn.timezInterfaceImpl.parent = timezColumn
|
||||
timezColumn.ColumnExpressionImpl = NewColumnImpl(name, "", timezColumn)
|
||||
|
||||
return timezColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnTimestamp is interface of SQL timestamp columns.
|
||||
type ColumnTimestamp interface {
|
||||
TimestampExpression
|
||||
Column
|
||||
|
||||
From(subQuery SelectTable) ColumnTimestamp
|
||||
SET(timestampExp TimestampExpression) ColumnAssigment
|
||||
}
|
||||
|
||||
type timestampColumnImpl struct {
|
||||
timestampInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *timestampColumnImpl) From(subQuery SelectTable) ColumnTimestamp {
|
||||
newTimestampColumn := TimestampColumn(i.name)
|
||||
newTimestampColumn.setTableName(i.tableName)
|
||||
newTimestampColumn.setSubQuery(subQuery)
|
||||
|
||||
return newTimestampColumn
|
||||
}
|
||||
|
||||
func (i *timestampColumnImpl) SET(timestampExp TimestampExpression) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: i,
|
||||
expression: timestampExp,
|
||||
}
|
||||
}
|
||||
|
||||
// TimestampColumn creates named timestamp column
|
||||
func TimestampColumn(name string) ColumnTimestamp {
|
||||
timestampColumn := ×tampColumnImpl{}
|
||||
timestampColumn.timestampInterfaceImpl.parent = timestampColumn
|
||||
timestampColumn.ColumnExpressionImpl = NewColumnImpl(name, "", timestampColumn)
|
||||
|
||||
return timestampColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnTimestampz is interface of SQL timestamp with timezone columns.
|
||||
type ColumnTimestampz interface {
|
||||
TimestampzExpression
|
||||
Column
|
||||
|
||||
From(subQuery SelectTable) ColumnTimestampz
|
||||
SET(timestampzExp TimestampzExpression) ColumnAssigment
|
||||
}
|
||||
|
||||
type timestampzColumnImpl struct {
|
||||
timestampzInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *timestampzColumnImpl) From(subQuery SelectTable) ColumnTimestampz {
|
||||
newTimestampzColumn := TimestampzColumn(i.name)
|
||||
newTimestampzColumn.setTableName(i.tableName)
|
||||
newTimestampzColumn.setSubQuery(subQuery)
|
||||
|
||||
return newTimestampzColumn
|
||||
}
|
||||
|
||||
func (i *timestampzColumnImpl) SET(timestampzExp TimestampzExpression) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: i,
|
||||
expression: timestampzExp,
|
||||
}
|
||||
}
|
||||
|
||||
// TimestampzColumn creates named timestamp with time zone column.
|
||||
func TimestampzColumn(name string) ColumnTimestampz {
|
||||
timestampzColumn := ×tampzColumnImpl{}
|
||||
timestampzColumn.timestampzInterfaceImpl.parent = timestampzColumn
|
||||
timestampzColumn.ColumnExpressionImpl = NewColumnImpl(name, "", timestampzColumn)
|
||||
|
||||
return timestampzColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnDate is interface of SQL date columns.
|
||||
type ColumnDate interface {
|
||||
DateExpression
|
||||
Column
|
||||
|
||||
From(subQuery SelectTable) ColumnDate
|
||||
SET(dateExp DateExpression) ColumnAssigment
|
||||
}
|
||||
|
||||
type dateColumnImpl struct {
|
||||
dateInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *dateColumnImpl) From(subQuery SelectTable) ColumnDate {
|
||||
newDateColumn := DateColumn(i.name)
|
||||
newDateColumn.setTableName(i.tableName)
|
||||
newDateColumn.setSubQuery(subQuery)
|
||||
|
||||
return newDateColumn
|
||||
}
|
||||
|
||||
func (i *dateColumnImpl) SET(dateExp DateExpression) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: i,
|
||||
expression: dateExp,
|
||||
}
|
||||
}
|
||||
|
||||
// DateColumn creates named date column.
|
||||
func DateColumn(name string) ColumnDate {
|
||||
dateColumn := &dateColumnImpl{}
|
||||
dateColumn.dateInterfaceImpl.parent = dateColumn
|
||||
dateColumn.ColumnExpressionImpl = NewColumnImpl(name, "", dateColumn)
|
||||
return dateColumn
|
||||
}
|
||||
|
||||
//------------------------------------------------------//
|
||||
|
||||
// ColumnRange is interface for range columns which can be int range, string range
|
||||
// timestamp range or date range.
|
||||
type ColumnRange[T Expression] interface {
|
||||
Range[T]
|
||||
Column
|
||||
|
||||
From(subQuery SelectTable) ColumnRange[T]
|
||||
SET(rangeExp Range[T]) ColumnAssigment
|
||||
}
|
||||
|
||||
type rangeColumnImpl[T Expression] struct {
|
||||
rangeInterfaceImpl[T]
|
||||
ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *rangeColumnImpl[T]) From(subQuery SelectTable) ColumnRange[T] {
|
||||
newRangeColumn := RangeColumn[T](i.name)
|
||||
newRangeColumn.setTableName(i.tableName)
|
||||
newRangeColumn.setSubQuery(subQuery)
|
||||
|
||||
return newRangeColumn
|
||||
}
|
||||
|
||||
func (i *rangeColumnImpl[T]) SET(rangeExp Range[T]) ColumnAssigment {
|
||||
return columnAssigmentImpl{
|
||||
column: i,
|
||||
expression: rangeExp,
|
||||
}
|
||||
}
|
||||
|
||||
// RangeColumn creates named range column.
|
||||
func RangeColumn[T Expression](name string) ColumnRange[T] {
|
||||
rangeColumn := &rangeColumnImpl[T]{}
|
||||
rangeColumn.rangeInterfaceImpl.parent = rangeColumn
|
||||
rangeColumn.ColumnExpressionImpl = NewColumnImpl(name, "", rangeColumn)
|
||||
|
||||
return rangeColumn
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var subQuery = &selectTableImpl{
|
||||
alias: "sub_query",
|
||||
}
|
||||
|
||||
func TestNewBoolColumn(t *testing.T) {
|
||||
boolColumn := BoolColumn("colBool").From(subQuery)
|
||||
assertClauseSerialize(t, boolColumn, `sub_query."colBool"`)
|
||||
assertClauseSerialize(t, boolColumn.EQ(Bool(true)), `(sub_query."colBool" = $1)`, true)
|
||||
assertProjectionSerialize(t, boolColumn, `sub_query."colBool" AS "colBool"`)
|
||||
|
||||
boolColumn2 := table1ColBool.From(subQuery)
|
||||
assertClauseSerialize(t, boolColumn2, `sub_query."table1.col_bool"`)
|
||||
assertClauseSerialize(t, boolColumn2.EQ(Bool(true)), `(sub_query."table1.col_bool" = $1)`, true)
|
||||
assertProjectionSerialize(t, boolColumn2, `sub_query."table1.col_bool" AS "table1.col_bool"`)
|
||||
}
|
||||
|
||||
func TestNewIntColumn(t *testing.T) {
|
||||
intColumn := IntegerColumn("col_int").From(subQuery)
|
||||
assertClauseSerialize(t, intColumn, `sub_query.col_int`)
|
||||
assertClauseSerialize(t, intColumn.EQ(Int(12)), `(sub_query.col_int = $1)`, int64(12))
|
||||
assertProjectionSerialize(t, intColumn, `sub_query.col_int AS "col_int"`)
|
||||
|
||||
intColumn2 := table1ColInt.From(subQuery)
|
||||
assertClauseSerialize(t, intColumn2, `sub_query."table1.col_int"`)
|
||||
assertClauseSerialize(t, intColumn2.EQ(Int(14)), `(sub_query."table1.col_int" = $1)`, int64(14))
|
||||
assertProjectionSerialize(t, intColumn2, `sub_query."table1.col_int" AS "table1.col_int"`)
|
||||
|
||||
}
|
||||
|
||||
func TestNewFloatColumnColumn(t *testing.T) {
|
||||
floatColumn := FloatColumn("col_float").From(subQuery)
|
||||
assertClauseSerialize(t, floatColumn, `sub_query.col_float`)
|
||||
assertClauseSerialize(t, floatColumn.EQ(Float(1.11)), `(sub_query.col_float = $1)`, float64(1.11))
|
||||
assertProjectionSerialize(t, floatColumn, `sub_query.col_float AS "col_float"`)
|
||||
|
||||
floatColumn2 := table1ColFloat.From(subQuery)
|
||||
assertClauseSerialize(t, floatColumn2, `sub_query."table1.col_float"`)
|
||||
assertClauseSerialize(t, floatColumn2.EQ(Float(2.22)), `(sub_query."table1.col_float" = $1)`, float64(2.22))
|
||||
assertProjectionSerialize(t, floatColumn2, `sub_query."table1.col_float" AS "table1.col_float"`)
|
||||
}
|
||||
|
||||
func TestNewDateColumnColumn(t *testing.T) {
|
||||
dateColumn := DateColumn("col_date").From(subQuery)
|
||||
assertClauseSerialize(t, dateColumn, `sub_query.col_date`)
|
||||
assertClauseSerialize(t, dateColumn.EQ(Date(2002, 2, 3)),
|
||||
`(sub_query.col_date = $1)`, "2002-02-03")
|
||||
assertProjectionSerialize(t, dateColumn, `sub_query.col_date AS "col_date"`)
|
||||
|
||||
dateColumn2 := table1ColDate.From(subQuery)
|
||||
assertClauseSerialize(t, dateColumn2, `sub_query."table1.col_date"`)
|
||||
assertClauseSerialize(t, dateColumn2.EQ(Date(2002, 2, 3)),
|
||||
`(sub_query."table1.col_date" = $1)`, "2002-02-03")
|
||||
assertProjectionSerialize(t, dateColumn2, `sub_query."table1.col_date" AS "table1.col_date"`)
|
||||
}
|
||||
|
||||
func TestNewTimeColumnColumn(t *testing.T) {
|
||||
timeColumn := TimeColumn("col_time").From(subQuery)
|
||||
assertClauseSerialize(t, timeColumn, `sub_query.col_time`)
|
||||
assertClauseSerialize(t, timeColumn.EQ(Time(1, 1, 1, 1)),
|
||||
`(sub_query.col_time = $1)`, "01:01:01.000000001")
|
||||
assertProjectionSerialize(t, timeColumn, `sub_query.col_time AS "col_time"`)
|
||||
|
||||
timeColumn2 := table1ColTime.From(subQuery)
|
||||
assertClauseSerialize(t, timeColumn2, `sub_query."table1.col_time"`)
|
||||
assertClauseSerialize(t, timeColumn2.EQ(Time(2, 2, 2)),
|
||||
`(sub_query."table1.col_time" = $1)`, "02:02:02")
|
||||
assertProjectionSerialize(t, timeColumn2, `sub_query."table1.col_time" AS "table1.col_time"`)
|
||||
}
|
||||
|
||||
func TestNewTimezColumnColumn(t *testing.T) {
|
||||
timezColumn := TimezColumn("col_timez").From(subQuery)
|
||||
assertClauseSerialize(t, timezColumn, `sub_query.col_timez`)
|
||||
assertClauseSerialize(t, timezColumn.EQ(Timez(1, 1, 1, 1, "UTC")),
|
||||
`(sub_query.col_timez = $1)`, "01:01:01.000000001 UTC")
|
||||
assertProjectionSerialize(t, timezColumn, `sub_query.col_timez AS "col_timez"`)
|
||||
|
||||
timezColumn2 := table1ColTimez.From(subQuery)
|
||||
assertClauseSerialize(t, timezColumn2, `sub_query."table1.col_timez"`)
|
||||
assertClauseSerialize(t, timezColumn2.EQ(Timez(2, 2, 2, 0, "UTC")),
|
||||
`(sub_query."table1.col_timez" = $1)`, "02:02:02 UTC")
|
||||
assertProjectionSerialize(t, timezColumn2, `sub_query."table1.col_timez" AS "table1.col_timez"`)
|
||||
}
|
||||
|
||||
func TestNewTimestampColumnColumn(t *testing.T) {
|
||||
timestampColumn := TimestampColumn("col_timestamp").From(subQuery)
|
||||
assertClauseSerialize(t, timestampColumn, `sub_query.col_timestamp`)
|
||||
assertClauseSerialize(t, timestampColumn.EQ(Timestamp(1, 1, 1, 1, 1, 1)),
|
||||
`(sub_query.col_timestamp = $1)`, "0001-01-01 01:01:01")
|
||||
assertProjectionSerialize(t, timestampColumn, `sub_query.col_timestamp AS "col_timestamp"`)
|
||||
|
||||
timestampColumn2 := table1ColTimestamp.From(subQuery)
|
||||
assertClauseSerialize(t, timestampColumn2, `sub_query."table1.col_timestamp"`)
|
||||
assertClauseSerialize(t, timestampColumn2.EQ(Timestamp(2, 2, 2, 2, 2, 2)),
|
||||
`(sub_query."table1.col_timestamp" = $1)`, "0002-02-02 02:02:02")
|
||||
assertProjectionSerialize(t, timestampColumn2, `sub_query."table1.col_timestamp" AS "table1.col_timestamp"`)
|
||||
}
|
||||
|
||||
func TestNewTimestampzColumnColumn(t *testing.T) {
|
||||
timestampzColumn := TimestampzColumn("col_timestampz").From(subQuery)
|
||||
assertClauseSerialize(t, timestampzColumn, `sub_query.col_timestampz`)
|
||||
assertClauseSerialize(t, timestampzColumn.EQ(Timestampz(1, 1, 1, 1, 1, 1, 0, "UTC")),
|
||||
`(sub_query.col_timestampz = $1)`, "0001-01-01 01:01:01 UTC")
|
||||
assertProjectionSerialize(t, timestampzColumn, `sub_query.col_timestampz AS "col_timestampz"`)
|
||||
|
||||
timestampzColumn2 := table1ColTimestampz.From(subQuery)
|
||||
assertClauseSerialize(t, timestampzColumn2, `sub_query."table1.col_timestampz"`)
|
||||
assertClauseSerialize(t, timestampzColumn2.EQ(Timestampz(2, 2, 2, 2, 2, 2, 0, "UTC")),
|
||||
`(sub_query."table1.col_timestampz" = $1)`, "0002-02-02 02:02:02 UTC")
|
||||
assertProjectionSerialize(t, timestampzColumn2, `sub_query."table1.col_timestampz" AS "table1.col_timestampz"`)
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package jet
|
||||
|
||||
// DateExpression is interface for date types
|
||||
type DateExpression interface {
|
||||
Expression
|
||||
|
||||
EQ(rhs DateExpression) BoolExpression
|
||||
NOT_EQ(rhs DateExpression) BoolExpression
|
||||
IS_DISTINCT_FROM(rhs DateExpression) BoolExpression
|
||||
IS_NOT_DISTINCT_FROM(rhs DateExpression) BoolExpression
|
||||
|
||||
LT(rhs DateExpression) BoolExpression
|
||||
LT_EQ(rhs DateExpression) BoolExpression
|
||||
GT(rhs DateExpression) BoolExpression
|
||||
GT_EQ(rhs DateExpression) BoolExpression
|
||||
BETWEEN(min, max DateExpression) BoolExpression
|
||||
NOT_BETWEEN(min, max DateExpression) BoolExpression
|
||||
|
||||
ADD(rhs Interval) TimestampExpression
|
||||
SUB(rhs Interval) TimestampExpression
|
||||
}
|
||||
|
||||
type dateInterfaceImpl struct {
|
||||
parent DateExpression
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) EQ(rhs DateExpression) BoolExpression {
|
||||
return Eq(d.parent, rhs)
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) NOT_EQ(rhs DateExpression) BoolExpression {
|
||||
return NotEq(d.parent, rhs)
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) IS_DISTINCT_FROM(rhs DateExpression) BoolExpression {
|
||||
return IsDistinctFrom(d.parent, rhs)
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs DateExpression) BoolExpression {
|
||||
return IsNotDistinctFrom(d.parent, rhs)
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) LT(rhs DateExpression) BoolExpression {
|
||||
return Lt(d.parent, rhs)
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) LT_EQ(rhs DateExpression) BoolExpression {
|
||||
return LtEq(d.parent, rhs)
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) GT(rhs DateExpression) BoolExpression {
|
||||
return Gt(d.parent, rhs)
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) GT_EQ(rhs DateExpression) BoolExpression {
|
||||
return GtEq(d.parent, rhs)
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) BETWEEN(min, max DateExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(d.parent, min, max, false)
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) NOT_BETWEEN(min, max DateExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(d.parent, min, max, true)
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) ADD(rhs Interval) TimestampExpression {
|
||||
return TimestampExp(Add(d.parent, rhs))
|
||||
}
|
||||
|
||||
func (d *dateInterfaceImpl) SUB(rhs Interval) TimestampExpression {
|
||||
return TimestampExp(Sub(d.parent, rhs))
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type dateExpressionWrapper struct {
|
||||
dateInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newDateExpressionWrap(expression Expression) DateExpression {
|
||||
dateExpressionWrap := dateExpressionWrapper{Expression: expression}
|
||||
dateExpressionWrap.dateInterfaceImpl.parent = &dateExpressionWrap
|
||||
return &dateExpressionWrap
|
||||
}
|
||||
|
||||
// DateExp is date expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as date expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func DateExp(expression Expression) DateExpression {
|
||||
return newDateExpressionWrap(expression)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDateArithmetic(t *testing.T) {
|
||||
timestamp := Timestamp(2000, 1, 1, 0, 0, 0)
|
||||
assertClauseDebugSerialize(t, table1ColDate.ADD(NewInterval(String("1 HOUR"))).EQ(timestamp),
|
||||
"((table1.col_date + INTERVAL '1 HOUR') = '2000-01-01 00:00:00')")
|
||||
assertClauseDebugSerialize(t, table1ColDate.SUB(NewInterval(String("1 HOUR"))).EQ(timestamp),
|
||||
"((table1.col_date - INTERVAL '1 HOUR') = '2000-01-01 00:00:00')")
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package jet
|
||||
|
||||
import "strings"
|
||||
|
||||
// Dialect interface
|
||||
type Dialect interface {
|
||||
Name() string
|
||||
PackageName() string
|
||||
OperatorSerializeOverride(operator string) SerializeOverride
|
||||
FunctionSerializeOverride(function string) SerializeOverride
|
||||
AliasQuoteChar() byte
|
||||
IdentifierQuoteChar() byte
|
||||
ArgumentPlaceholder() QueryPlaceholderFunc
|
||||
IsReservedWord(name string) bool
|
||||
SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
||||
ValuesDefaultColumnName(index int) string
|
||||
}
|
||||
|
||||
// SerializerFunc func
|
||||
type SerializerFunc func(statement StatementType, out *SQLBuilder, options ...SerializeOption)
|
||||
|
||||
// SerializeOverride func
|
||||
type SerializeOverride func(expressions ...Serializer) SerializerFunc
|
||||
|
||||
// QueryPlaceholderFunc func
|
||||
type QueryPlaceholderFunc func(ord int) string
|
||||
|
||||
// DialectParams struct
|
||||
type DialectParams struct {
|
||||
Name string
|
||||
PackageName string
|
||||
OperatorSerializeOverrides map[string]SerializeOverride
|
||||
FunctionSerializeOverrides map[string]SerializeOverride
|
||||
AliasQuoteChar byte
|
||||
IdentifierQuoteChar byte
|
||||
ArgumentPlaceholder QueryPlaceholderFunc
|
||||
ReservedWords []string
|
||||
SerializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
||||
ValuesDefaultColumnName func(index int) string
|
||||
}
|
||||
|
||||
// NewDialect creates new dialect with params
|
||||
func NewDialect(params DialectParams) Dialect {
|
||||
return &dialectImpl{
|
||||
name: params.Name,
|
||||
packageName: params.PackageName,
|
||||
operatorSerializeOverrides: params.OperatorSerializeOverrides,
|
||||
functionSerializeOverrides: params.FunctionSerializeOverrides,
|
||||
aliasQuoteChar: params.AliasQuoteChar,
|
||||
identifierQuoteChar: params.IdentifierQuoteChar,
|
||||
argumentPlaceholder: params.ArgumentPlaceholder,
|
||||
reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords),
|
||||
serializeOrderBy: params.SerializeOrderBy,
|
||||
valuesDefaultColumnName: params.ValuesDefaultColumnName,
|
||||
}
|
||||
}
|
||||
|
||||
type dialectImpl struct {
|
||||
name string
|
||||
packageName string
|
||||
operatorSerializeOverrides map[string]SerializeOverride
|
||||
functionSerializeOverrides map[string]SerializeOverride
|
||||
aliasQuoteChar byte
|
||||
identifierQuoteChar byte
|
||||
argumentPlaceholder QueryPlaceholderFunc
|
||||
reservedWords map[string]bool
|
||||
serializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
||||
valuesDefaultColumnName func(index int) string
|
||||
}
|
||||
|
||||
func (d *dialectImpl) Name() string {
|
||||
return d.name
|
||||
}
|
||||
|
||||
func (d *dialectImpl) PackageName() string {
|
||||
return d.packageName
|
||||
}
|
||||
|
||||
func (d *dialectImpl) OperatorSerializeOverride(operator string) SerializeOverride {
|
||||
if d.operatorSerializeOverrides == nil {
|
||||
return nil
|
||||
}
|
||||
return d.operatorSerializeOverrides[operator]
|
||||
}
|
||||
|
||||
func (d *dialectImpl) FunctionSerializeOverride(function string) SerializeOverride {
|
||||
if d.functionSerializeOverrides == nil {
|
||||
return nil
|
||||
}
|
||||
return d.functionSerializeOverrides[function]
|
||||
}
|
||||
|
||||
func (d *dialectImpl) AliasQuoteChar() byte {
|
||||
return d.aliasQuoteChar
|
||||
}
|
||||
|
||||
func (d *dialectImpl) IdentifierQuoteChar() byte {
|
||||
return d.identifierQuoteChar
|
||||
}
|
||||
|
||||
func (d *dialectImpl) ArgumentPlaceholder() QueryPlaceholderFunc {
|
||||
return d.argumentPlaceholder
|
||||
}
|
||||
|
||||
func (d *dialectImpl) IsReservedWord(name string) bool {
|
||||
_, isReservedWord := d.reservedWords[strings.ToLower(name)]
|
||||
return isReservedWord
|
||||
}
|
||||
|
||||
func (d *dialectImpl) SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc {
|
||||
return d.serializeOrderBy
|
||||
}
|
||||
|
||||
func (d *dialectImpl) ValuesDefaultColumnName(index int) string {
|
||||
return d.valuesDefaultColumnName(index)
|
||||
}
|
||||
|
||||
func arrayOfStringsToMapOfStrings(arr []string) map[string]bool {
|
||||
ret := map[string]bool{}
|
||||
for _, elem := range arr {
|
||||
ret[strings.ToLower(elem)] = true
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package jet
|
||||
|
||||
type enumValue struct {
|
||||
ExpressionInterfaceImpl
|
||||
stringInterfaceImpl
|
||||
|
||||
name string
|
||||
}
|
||||
|
||||
// NewEnumValue creates new named enum value
|
||||
func NewEnumValue(name string) StringExpression {
|
||||
enumValue := &enumValue{name: name}
|
||||
|
||||
enumValue.ExpressionInterfaceImpl.Parent = enumValue
|
||||
enumValue.stringInterfaceImpl.parent = enumValue
|
||||
|
||||
return enumValue
|
||||
}
|
||||
|
||||
func (e enumValue) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.insertConstantArgument(e.name)
|
||||
}
|
||||
@@ -1,321 +0,0 @@
|
||||
package jet
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Expression is common interface for all expressions.
|
||||
// Can be Bool, Int, Float, String, Date, Time, Timez, Timestamp or Timestampz expressions.
|
||||
type Expression interface {
|
||||
Serializer
|
||||
Projection
|
||||
GroupByClause
|
||||
OrderByClause
|
||||
|
||||
// IS_NULL tests expression whether it is a NULL value.
|
||||
IS_NULL() BoolExpression
|
||||
// IS_NOT_NULL tests expression whether it is a non-NULL value.
|
||||
IS_NOT_NULL() BoolExpression
|
||||
|
||||
// IN checks if this expressions matches any in expressions list
|
||||
IN(expressions ...Expression) BoolExpression
|
||||
// NOT_IN checks if this expressions is different of all expressions in expressions list
|
||||
NOT_IN(expressions ...Expression) BoolExpression
|
||||
|
||||
// AS the temporary alias name to assign to the expression
|
||||
AS(alias string) Projection
|
||||
|
||||
// ASC expression will be used to sort query result in ascending order
|
||||
ASC() OrderByClause
|
||||
// DESC expression will be used to sort query result in descending order
|
||||
DESC() OrderByClause
|
||||
}
|
||||
|
||||
// ExpressionInterfaceImpl implements Expression interface methods
|
||||
type ExpressionInterfaceImpl struct {
|
||||
Parent Expression
|
||||
}
|
||||
|
||||
func (e *ExpressionInterfaceImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
panic(fmt.Sprintf("jet: can't export unaliased expression subQuery: %s, expression: %s",
|
||||
subQuery.Alias(), serializeToDefaultDebugString(e.Parent)))
|
||||
}
|
||||
|
||||
// IS_NULL tests expression whether it is a NULL value.
|
||||
func (e *ExpressionInterfaceImpl) IS_NULL() BoolExpression {
|
||||
return newPostfixBoolOperatorExpression(e.Parent, "IS NULL")
|
||||
}
|
||||
|
||||
// IS_NOT_NULL tests expression whether it is a non-NULL value.
|
||||
func (e *ExpressionInterfaceImpl) IS_NOT_NULL() BoolExpression {
|
||||
return newPostfixBoolOperatorExpression(e.Parent, "IS NOT NULL")
|
||||
}
|
||||
|
||||
// IN checks if this expressions matches any in expressions list
|
||||
func (e *ExpressionInterfaceImpl) IN(expressions ...Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(e.Parent, wrap(expressions...), "IN")
|
||||
}
|
||||
|
||||
// NOT_IN checks if this expressions is different of all expressions in expressions list
|
||||
func (e *ExpressionInterfaceImpl) NOT_IN(expressions ...Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(e.Parent, wrap(expressions...), "NOT IN")
|
||||
}
|
||||
|
||||
// AS the temporary alias name to assign to the expression
|
||||
func (e *ExpressionInterfaceImpl) AS(alias string) Projection {
|
||||
return newAlias(e.Parent, alias)
|
||||
}
|
||||
|
||||
// ASC expression will be used to sort a query result in ascending order
|
||||
func (e *ExpressionInterfaceImpl) ASC() OrderByClause {
|
||||
return newOrderByAscending(e.Parent, true)
|
||||
}
|
||||
|
||||
// DESC expression will be used to sort a query result in descending order
|
||||
func (e *ExpressionInterfaceImpl) DESC() OrderByClause {
|
||||
return newOrderByAscending(e.Parent, false)
|
||||
}
|
||||
|
||||
// NULLS_FIRST specifies sort where null values appear before all non-null values
|
||||
func (e *ExpressionInterfaceImpl) NULLS_FIRST() OrderByClause {
|
||||
return newOrderByNullsFirst(e.Parent, true)
|
||||
}
|
||||
|
||||
// NULLS_LAST specifies sort where null values appear after all non-null values
|
||||
func (e *ExpressionInterfaceImpl) NULLS_LAST() OrderByClause {
|
||||
return newOrderByNullsFirst(e.Parent, false)
|
||||
}
|
||||
|
||||
func (e *ExpressionInterfaceImpl) serializeForGroupBy(statement StatementType, out *SQLBuilder) {
|
||||
e.Parent.serialize(statement, out, NoWrap)
|
||||
}
|
||||
|
||||
func (e *ExpressionInterfaceImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||
e.Parent.serialize(statement, out, NoWrap)
|
||||
}
|
||||
|
||||
func (e *ExpressionInterfaceImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
|
||||
e.Parent.serialize(statement, out, NoWrap)
|
||||
}
|
||||
|
||||
// Representation of binary operations (e.g. comparisons, arithmetic)
|
||||
type binaryOperatorExpression struct {
|
||||
ExpressionInterfaceImpl
|
||||
|
||||
lhs, rhs Serializer
|
||||
additionalParam Serializer
|
||||
operator string
|
||||
}
|
||||
|
||||
// NewBinaryOperatorExpression creates new binaryOperatorExpression
|
||||
func NewBinaryOperatorExpression(lhs, rhs Serializer, operator string, additionalParam ...Expression) Expression {
|
||||
binaryExpression := &binaryOperatorExpression{
|
||||
lhs: lhs,
|
||||
rhs: rhs,
|
||||
operator: operator,
|
||||
}
|
||||
|
||||
if len(additionalParam) > 0 {
|
||||
binaryExpression.additionalParam = additionalParam[0]
|
||||
}
|
||||
|
||||
binaryExpression.ExpressionInterfaceImpl.Parent = binaryExpression
|
||||
|
||||
return complexExpr(binaryExpression)
|
||||
}
|
||||
|
||||
func (c *binaryOperatorExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if serializeOverride := out.Dialect.OperatorSerializeOverride(c.operator); serializeOverride != nil {
|
||||
serializeOverrideFunc := serializeOverride(c.lhs, c.rhs, c.additionalParam)
|
||||
serializeOverrideFunc(statement, out, FallTrough(options)...)
|
||||
} else {
|
||||
c.lhs.serialize(statement, out, FallTrough(options)...)
|
||||
out.WriteString(c.operator)
|
||||
c.rhs.serialize(statement, out, FallTrough(options)...)
|
||||
}
|
||||
}
|
||||
|
||||
type expressionListOperator struct {
|
||||
ExpressionInterfaceImpl
|
||||
|
||||
operator string
|
||||
expressions []Expression
|
||||
}
|
||||
|
||||
func newExpressionListOperator(operator string, expressions ...Expression) *expressionListOperator {
|
||||
ret := &expressionListOperator{
|
||||
operator: operator,
|
||||
expressions: expressions,
|
||||
}
|
||||
|
||||
ret.ExpressionInterfaceImpl.Parent = ret
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func newBoolExpressionListOperator(operator string, expressions ...BoolExpression) BoolExpression {
|
||||
return BoolExp(newExpressionListOperator(operator, BoolExpressionListToExpressionList(expressions)...))
|
||||
}
|
||||
|
||||
func (elo *expressionListOperator) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if len(elo.expressions) == 0 {
|
||||
panic("jet: syntax error, expression list empty")
|
||||
}
|
||||
|
||||
shouldWrap := len(elo.expressions) > 1
|
||||
if shouldWrap {
|
||||
out.WriteByte('(')
|
||||
out.IncreaseIdent(tabSize)
|
||||
out.NewLine()
|
||||
}
|
||||
|
||||
for i, expression := range elo.expressions {
|
||||
if i == 1 {
|
||||
out.IncreaseIdent(tabSize)
|
||||
}
|
||||
if i > 0 {
|
||||
out.NewLine()
|
||||
out.WriteString(elo.operator)
|
||||
}
|
||||
|
||||
out.IncreaseIdent(len(elo.operator) + 1)
|
||||
expression.serialize(statement, out, FallTrough(options)...)
|
||||
out.DecreaseIdent(len(elo.operator) + 1)
|
||||
}
|
||||
|
||||
if len(elo.expressions) > 1 {
|
||||
out.DecreaseIdent(tabSize)
|
||||
}
|
||||
|
||||
if shouldWrap {
|
||||
out.DecreaseIdent(tabSize)
|
||||
out.NewLine()
|
||||
out.WriteByte(')')
|
||||
}
|
||||
}
|
||||
|
||||
// A prefix operator Expression
|
||||
type prefixExpression struct {
|
||||
ExpressionInterfaceImpl
|
||||
|
||||
expression Expression
|
||||
operator string
|
||||
}
|
||||
|
||||
func newPrefixOperatorExpression(expression Expression, operator string) Expression {
|
||||
prefixExpression := &prefixExpression{
|
||||
expression: expression,
|
||||
operator: operator,
|
||||
}
|
||||
prefixExpression.ExpressionInterfaceImpl.Parent = prefixExpression
|
||||
|
||||
return complexExpr(prefixExpression)
|
||||
}
|
||||
|
||||
func (p *prefixExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.WriteString(p.operator)
|
||||
p.expression.serialize(statement, out, FallTrough(options)...)
|
||||
}
|
||||
|
||||
// A postfix operator Expression
|
||||
type postfixOpExpression struct {
|
||||
ExpressionInterfaceImpl
|
||||
|
||||
expression Expression
|
||||
operator string
|
||||
}
|
||||
|
||||
func newPostfixOperatorExpression(expression Expression, operator string) *postfixOpExpression {
|
||||
postfixOpExpression := &postfixOpExpression{
|
||||
expression: expression,
|
||||
operator: operator,
|
||||
}
|
||||
|
||||
postfixOpExpression.ExpressionInterfaceImpl.Parent = postfixOpExpression
|
||||
|
||||
return postfixOpExpression
|
||||
}
|
||||
|
||||
func (p *postfixOpExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
p.expression.serialize(statement, out, FallTrough(options)...)
|
||||
out.WriteString(p.operator)
|
||||
}
|
||||
|
||||
type betweenOperatorExpression struct {
|
||||
ExpressionInterfaceImpl
|
||||
|
||||
expression Expression
|
||||
notBetween bool
|
||||
min Expression
|
||||
max Expression
|
||||
}
|
||||
|
||||
// NewBetweenOperatorExpression creates new BETWEEN operator expression
|
||||
func NewBetweenOperatorExpression(expression, min, max Expression, notBetween bool) BoolExpression {
|
||||
newBetweenOperator := &betweenOperatorExpression{
|
||||
expression: expression,
|
||||
notBetween: notBetween,
|
||||
min: min,
|
||||
max: max,
|
||||
}
|
||||
|
||||
newBetweenOperator.ExpressionInterfaceImpl.Parent = newBetweenOperator
|
||||
|
||||
return BoolExp(complexExpr(newBetweenOperator))
|
||||
}
|
||||
|
||||
func (p *betweenOperatorExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
p.expression.serialize(statement, out, FallTrough(options)...)
|
||||
if p.notBetween {
|
||||
out.WriteString("NOT")
|
||||
}
|
||||
out.WriteString("BETWEEN")
|
||||
p.min.serialize(statement, out, FallTrough(options)...)
|
||||
out.WriteString("AND")
|
||||
p.max.serialize(statement, out, FallTrough(options)...)
|
||||
}
|
||||
|
||||
type customExpression struct {
|
||||
ExpressionInterfaceImpl
|
||||
parts []Serializer
|
||||
}
|
||||
|
||||
func CustomExpression(parts ...Serializer) Expression {
|
||||
ret := customExpression{
|
||||
parts: parts,
|
||||
}
|
||||
ret.ExpressionInterfaceImpl.Parent = &ret
|
||||
return &ret
|
||||
}
|
||||
|
||||
func (c *customExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
for _, expression := range c.parts {
|
||||
expression.serialize(statement, out, options...)
|
||||
}
|
||||
}
|
||||
|
||||
type complexExpression struct {
|
||||
ExpressionInterfaceImpl
|
||||
expressions Expression
|
||||
}
|
||||
|
||||
func complexExpr(expression Expression) Expression {
|
||||
complexExpression := &complexExpression{expressions: expression}
|
||||
complexExpression.ExpressionInterfaceImpl.Parent = complexExpression
|
||||
|
||||
return complexExpression
|
||||
}
|
||||
|
||||
func (s *complexExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if !contains(options, NoWrap) {
|
||||
out.WriteString("(")
|
||||
}
|
||||
|
||||
s.expressions.serialize(statement, out, options...) // FallTrough here because complexExpression is just a wrapper
|
||||
|
||||
if !contains(options, NoWrap) {
|
||||
out.WriteString(")")
|
||||
}
|
||||
}
|
||||
|
||||
func wrap(expressions ...Expression) Expression {
|
||||
return NewFunc("", expressions, nil)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExpressionIS_NULL(t *testing.T) {
|
||||
assertClauseSerialize(t, table2Col3.IS_NULL(), "table2.col3 IS NULL")
|
||||
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NULL(), "(table2.col3 + table2.col3) IS NULL")
|
||||
}
|
||||
|
||||
func TestExpressionIS_NOT_NULL(t *testing.T) {
|
||||
assertClauseSerialize(t, table2Col3.IS_NOT_NULL(), "table2.col3 IS NOT NULL")
|
||||
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NOT_NULL(), "(table2.col3 + table2.col3) IS NOT NULL")
|
||||
}
|
||||
|
||||
func TestExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table2Col3.IS_DISTINCT_FROM(table2Col4), "(table2.col3 IS DISTINCT FROM table2.col4)")
|
||||
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_DISTINCT_FROM(Int(23)), "((table2.col3 + table2.col3) IS DISTINCT FROM $1)", int64(23))
|
||||
}
|
||||
|
||||
func TestExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table2Col3.IS_NOT_DISTINCT_FROM(table2Col4), "(table2.col3 IS NOT DISTINCT FROM table2.col4)")
|
||||
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NOT_DISTINCT_FROM(Int(23)), "((table2.col3 + table2.col3) IS NOT DISTINCT FROM $1)", int64(23))
|
||||
}
|
||||
|
||||
func TestIN(t *testing.T) {
|
||||
assertClauseSerialize(t, table2ColInt.IN(Int(1), Int(2), Int(3)),
|
||||
`(table2.col_int IN ($1, $2, $3))`, int64(1), int64(2), int64(3))
|
||||
|
||||
}
|
||||
|
||||
func TestNOT_IN(t *testing.T) {
|
||||
|
||||
assertClauseSerialize(t, table2ColInt.NOT_IN(Int(1), Int(2), Int(3)),
|
||||
`(table2.col_int NOT IN ($1, $2, $3))`, int64(1), int64(2), int64(3))
|
||||
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package jet
|
||||
|
||||
// FloatExpression is interface for SQL float columns
|
||||
type FloatExpression interface {
|
||||
Expression
|
||||
numericExpression
|
||||
|
||||
EQ(rhs FloatExpression) BoolExpression
|
||||
NOT_EQ(rhs FloatExpression) BoolExpression
|
||||
IS_DISTINCT_FROM(rhs FloatExpression) BoolExpression
|
||||
IS_NOT_DISTINCT_FROM(rhs FloatExpression) BoolExpression
|
||||
|
||||
LT(rhs FloatExpression) BoolExpression
|
||||
LT_EQ(rhs FloatExpression) BoolExpression
|
||||
GT(rhs FloatExpression) BoolExpression
|
||||
GT_EQ(rhs FloatExpression) BoolExpression
|
||||
BETWEEN(min, max FloatExpression) BoolExpression
|
||||
NOT_BETWEEN(min, max FloatExpression) BoolExpression
|
||||
|
||||
ADD(rhs NumericExpression) FloatExpression
|
||||
SUB(rhs NumericExpression) FloatExpression
|
||||
MUL(rhs NumericExpression) FloatExpression
|
||||
DIV(rhs NumericExpression) FloatExpression
|
||||
MOD(rhs NumericExpression) FloatExpression
|
||||
POW(rhs NumericExpression) FloatExpression
|
||||
}
|
||||
|
||||
type floatInterfaceImpl struct {
|
||||
numericExpressionImpl
|
||||
parent FloatExpression
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) EQ(rhs FloatExpression) BoolExpression {
|
||||
return Eq(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) NOT_EQ(rhs FloatExpression) BoolExpression {
|
||||
return NotEq(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) IS_DISTINCT_FROM(rhs FloatExpression) BoolExpression {
|
||||
return IsDistinctFrom(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs FloatExpression) BoolExpression {
|
||||
return IsNotDistinctFrom(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) GT(rhs FloatExpression) BoolExpression {
|
||||
return Gt(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) GT_EQ(rhs FloatExpression) BoolExpression {
|
||||
return GtEq(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) LT(rhs FloatExpression) BoolExpression {
|
||||
return Lt(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) LT_EQ(rhs FloatExpression) BoolExpression {
|
||||
return LtEq(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) BETWEEN(min, max FloatExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(n.parent, min, max, false)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) NOT_BETWEEN(min, max FloatExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(n.parent, min, max, true)
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) ADD(rhs NumericExpression) FloatExpression {
|
||||
return FloatExp(Add(n.parent, rhs))
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) SUB(rhs NumericExpression) FloatExpression {
|
||||
return FloatExp(Sub(n.parent, rhs))
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) MUL(rhs NumericExpression) FloatExpression {
|
||||
return FloatExp(Mul(n.parent, rhs))
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) DIV(rhs NumericExpression) FloatExpression {
|
||||
return FloatExp(Div(n.parent, rhs))
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) MOD(rhs NumericExpression) FloatExpression {
|
||||
return FloatExp(Mod(n.parent, rhs))
|
||||
}
|
||||
|
||||
func (n *floatInterfaceImpl) POW(rhs NumericExpression) FloatExpression {
|
||||
return POW(n.parent, rhs)
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type floatExpressionWrapper struct {
|
||||
floatInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newFloatExpressionWrap(expression Expression) FloatExpression {
|
||||
floatExpressionWrap := floatExpressionWrapper{Expression: expression}
|
||||
floatExpressionWrap.floatInterfaceImpl.parent = &floatExpressionWrap
|
||||
return &floatExpressionWrap
|
||||
}
|
||||
|
||||
// FloatExp is date expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as float expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func FloatExp(expression Expression) FloatExpression {
|
||||
return newFloatExpressionWrap(expression)
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFloatExpressionEQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.EQ(table2ColFloat), "(table1.col_float = table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.EQ(Float(2.11)), "(table1.col_float = $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.NOT_EQ(table2ColFloat), "(table1.col_float != table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.NOT_EQ(Float(2.11)), "(table1.col_float != $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.IS_DISTINCT_FROM(table2ColFloat), "(table1.col_float IS DISTINCT FROM table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.IS_DISTINCT_FROM(Float(2.11)), "(table1.col_float IS DISTINCT FROM $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.IS_NOT_DISTINCT_FROM(table2ColFloat), "(table1.col_float IS NOT DISTINCT FROM table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.IS_NOT_DISTINCT_FROM(Float(2.11)), "(table1.col_float IS NOT DISTINCT FROM $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionGT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.GT(table2ColFloat), "(table1.col_float > table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.GT(Float(2.11)), "(table1.col_float > $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionGT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.GT_EQ(table2ColFloat), "(table1.col_float >= table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.GT_EQ(Float(2.11)), "(table1.col_float >= $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionLT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.LT(table2ColFloat), "(table1.col_float < table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.LT(Float(2.11)), "(table1.col_float < $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionLT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.LT_EQ(table2ColFloat), "(table1.col_float <= table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.LT_EQ(Float(2.11)), "(table1.col_float <= $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionADD(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.ADD(table2ColFloat), "(table1.col_float + table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.ADD(Float(2.11)), "(table1.col_float + $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionSUB(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.SUB(table2ColFloat), "(table1.col_float - table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.SUB(Float(2.11)), "(table1.col_float - $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionMUL(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.MUL(table2ColFloat), "(table1.col_float * table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.MUL(Float(2.11)), "(table1.col_float * $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionDIV(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.DIV(table2ColFloat), "(table1.col_float / table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.DIV(Float(2.11)), "(table1.col_float / $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionMOD(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.MOD(table2ColFloat), "(table1.col_float % table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.MOD(Float(2.11)), "(table1.col_float % $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExpressionPOW(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColFloat.POW(table2ColFloat), "POW(table1.col_float, table2.col_float)")
|
||||
assertClauseSerialize(t, table1ColFloat.POW(Float(2.11)), "POW(table1.col_float, $1)", float64(2.11))
|
||||
}
|
||||
|
||||
func TestFloatExp(t *testing.T) {
|
||||
assertClauseSerialize(t, FloatExp(table1ColInt), "table1.col_int")
|
||||
assertClauseSerialize(t, FloatExp(table1ColInt.ADD(table3ColInt)), "(table1.col_int + table3.col_int)")
|
||||
assertClauseSerialize(t, FloatExp(table1ColInt.ADD(table3ColInt)).ADD(Float(11.11)),
|
||||
"((table1.col_int + table3.col_int) + $1)", float64(11.11))
|
||||
}
|
||||
@@ -1,926 +0,0 @@
|
||||
package jet
|
||||
|
||||
// AND function adds AND operator between expressions. This function can be used, instead of method AND,
|
||||
// to have a better inlining of a complex condition in the Go code and in the generated SQL.
|
||||
func AND(expressions ...BoolExpression) BoolExpression {
|
||||
return newBoolExpressionListOperator("AND", expressions...)
|
||||
}
|
||||
|
||||
// OR function adds OR operator between expressions. This function can be used, instead of method OR,
|
||||
// to have a better inlining of a complex condition in the Go code and in the generated SQL.
|
||||
func OR(expressions ...BoolExpression) BoolExpression {
|
||||
return newBoolExpressionListOperator("OR", expressions...)
|
||||
}
|
||||
|
||||
// ------------------ Mathematical functions ---------------//
|
||||
|
||||
// ABSf calculates absolute value from float expression
|
||||
func ABSf(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("ABS", floatExpression)
|
||||
}
|
||||
|
||||
// ABSi calculates absolute value from int expression
|
||||
func ABSi(integerExpression IntegerExpression) IntegerExpression {
|
||||
return newIntegerFunc("ABS", integerExpression)
|
||||
}
|
||||
|
||||
// POW calculates power of base with exponent
|
||||
func POW(base, exponent NumericExpression) FloatExpression {
|
||||
return NewFloatFunc("POW", base, exponent)
|
||||
}
|
||||
|
||||
// POWER calculates power of base with exponent
|
||||
func POWER(base, exponent NumericExpression) FloatExpression {
|
||||
return NewFloatFunc("POWER", base, exponent)
|
||||
}
|
||||
|
||||
// SQRT calculates square root of numeric expression
|
||||
func SQRT(numericExpression NumericExpression) FloatExpression {
|
||||
return NewFloatFunc("SQRT", numericExpression)
|
||||
}
|
||||
|
||||
// CBRT calculates cube root of numeric expression
|
||||
func CBRT(numericExpression NumericExpression) FloatExpression {
|
||||
return NewFloatFunc("CBRT", numericExpression)
|
||||
}
|
||||
|
||||
// CEIL calculates ceil of float expression
|
||||
func CEIL(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("CEIL", floatExpression)
|
||||
}
|
||||
|
||||
// FLOOR calculates floor of float expression
|
||||
func FLOOR(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("FLOOR", floatExpression)
|
||||
}
|
||||
|
||||
// ROUND calculates round of a float expressions with optional precision
|
||||
func ROUND(floatExpression FloatExpression, precision ...IntegerExpression) FloatExpression {
|
||||
if len(precision) > 0 {
|
||||
return NewFloatFunc("ROUND", floatExpression, precision[0])
|
||||
}
|
||||
return NewFloatFunc("ROUND", floatExpression)
|
||||
}
|
||||
|
||||
// SIGN returns sign of float expression
|
||||
func SIGN(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("SIGN", floatExpression)
|
||||
}
|
||||
|
||||
// TRUNC calculates trunc of float expression with optional precision
|
||||
func TRUNC(floatExpression FloatExpression, precision ...IntegerExpression) FloatExpression {
|
||||
if len(precision) > 0 {
|
||||
return NewFloatFunc("TRUNC", floatExpression, precision[0])
|
||||
}
|
||||
return NewFloatFunc("TRUNC", floatExpression)
|
||||
}
|
||||
|
||||
// LN calculates natural algorithm of float expression
|
||||
func LN(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("LN", floatExpression)
|
||||
}
|
||||
|
||||
// LOG calculates logarithm of float expression
|
||||
func LOG(floatExpression FloatExpression) FloatExpression {
|
||||
return NewFloatFunc("LOG", floatExpression)
|
||||
}
|
||||
|
||||
// ----------------- Aggregate functions -------------------//
|
||||
|
||||
// AVG is aggregate function used to calculate avg value from numeric expression
|
||||
func AVG(numericExpression Expression) floatWindowExpression {
|
||||
return NewFloatWindowFunc("AVG", numericExpression)
|
||||
}
|
||||
|
||||
// BIT_AND is aggregate function used to calculates the bitwise AND of all non-null input values, or null if none.
|
||||
func BIT_AND(integerExpression IntegerExpression) integerWindowExpression {
|
||||
return newIntegerWindowFunc("BIT_AND", integerExpression)
|
||||
}
|
||||
|
||||
// BIT_OR is aggregate function used to calculates the bitwise OR of all non-null input values, or null if none.
|
||||
func BIT_OR(integerExpression IntegerExpression) integerWindowExpression {
|
||||
return newIntegerWindowFunc("BIT_OR", integerExpression)
|
||||
}
|
||||
|
||||
// BOOL_AND is aggregate function. Returns true if all input values are true, otherwise false
|
||||
func BOOL_AND(boolExpression BoolExpression) boolWindowExpression {
|
||||
return newBoolWindowFunc("BOOL_AND", boolExpression)
|
||||
}
|
||||
|
||||
// BOOL_OR is aggregate function. Returns true if at least one input value is true, otherwise false
|
||||
func BOOL_OR(boolExpression BoolExpression) boolWindowExpression {
|
||||
return newBoolWindowFunc("BOOL_OR", boolExpression)
|
||||
}
|
||||
|
||||
// COUNT is aggregate function. Returns number of input rows for which the value of expression is not null.
|
||||
func COUNT(expression Expression) integerWindowExpression {
|
||||
return newIntegerWindowFunc("COUNT", expression)
|
||||
}
|
||||
|
||||
// EVERY is aggregate function. Returns true if all input values are true, otherwise false
|
||||
func EVERY(boolExpression BoolExpression) boolWindowExpression {
|
||||
return newBoolWindowFunc("EVERY", boolExpression)
|
||||
}
|
||||
|
||||
// MAX is aggregate function. Returns minimum value of expression across all input values.
|
||||
func MAX(expression Expression) Expression {
|
||||
return newWindowFunc("MAX", expression)
|
||||
}
|
||||
|
||||
// MAXf is aggregate function. Returns maximum value of float expression across all input values
|
||||
func MAXf(floatExpression FloatExpression) floatWindowExpression {
|
||||
return NewFloatWindowFunc("MAX", floatExpression)
|
||||
}
|
||||
|
||||
// MAXi is aggregate function. Returns maximum value of int expression across all input values
|
||||
func MAXi(integerExpression IntegerExpression) integerWindowExpression {
|
||||
return newIntegerWindowFunc("MAX", integerExpression)
|
||||
}
|
||||
|
||||
// MIN is aggregate function. Returns minimum value of expression across all input values.
|
||||
func MIN(expression Expression) Expression {
|
||||
return newWindowFunc("MIN", expression)
|
||||
}
|
||||
|
||||
// MINf is aggregate function. Returns minimum value of float expression across all input values
|
||||
func MINf(floatExpression FloatExpression) floatWindowExpression {
|
||||
return NewFloatWindowFunc("MIN", floatExpression)
|
||||
}
|
||||
|
||||
// MINi is aggregate function. Returns minimum value of int expression across all input values
|
||||
func MINi(integerExpression IntegerExpression) integerWindowExpression {
|
||||
return newIntegerWindowFunc("MIN", integerExpression)
|
||||
}
|
||||
|
||||
// SUM is aggregate function. Returns sum of all expressions
|
||||
func SUM(expression Expression) Expression {
|
||||
return newWindowFunc("SUM", expression)
|
||||
}
|
||||
|
||||
// SUMf is aggregate function. Returns sum of expression across all float expressions
|
||||
func SUMf(floatExpression FloatExpression) floatWindowExpression {
|
||||
return NewFloatWindowFunc("SUM", floatExpression)
|
||||
}
|
||||
|
||||
// SUMi is aggregate function. Returns sum of expression across all integer expression.
|
||||
func SUMi(integerExpression IntegerExpression) integerWindowExpression {
|
||||
return newIntegerWindowFunc("SUM", integerExpression)
|
||||
}
|
||||
|
||||
// ----------------- Window functions -------------------//
|
||||
|
||||
// ROW_NUMBER returns number of the current row within its partition, counting from 1
|
||||
func ROW_NUMBER() integerWindowExpression {
|
||||
return newIntegerWindowFunc("ROW_NUMBER")
|
||||
}
|
||||
|
||||
// RANK of the current row with gaps; same as row_number of its first peer
|
||||
func RANK() integerWindowExpression {
|
||||
return newIntegerWindowFunc("RANK")
|
||||
}
|
||||
|
||||
// DENSE_RANK returns rank of the current row without gaps; this function counts peer groups
|
||||
func DENSE_RANK() integerWindowExpression {
|
||||
return newIntegerWindowFunc("DENSE_RANK")
|
||||
}
|
||||
|
||||
// PERCENT_RANK calculates relative rank of the current row: (rank - 1) / (total partition rows - 1)
|
||||
func PERCENT_RANK() floatWindowExpression {
|
||||
return NewFloatWindowFunc("PERCENT_RANK")
|
||||
}
|
||||
|
||||
// CUME_DIST calculates cumulative distribution: (number of partition rows preceding or peer with current row) / total partition rows
|
||||
func CUME_DIST() floatWindowExpression {
|
||||
return NewFloatWindowFunc("CUME_DIST")
|
||||
}
|
||||
|
||||
// NTILE returns integer ranging from 1 to the argument value, dividing the partition as equally as possible
|
||||
func NTILE(numOfBuckets int64) integerWindowExpression {
|
||||
return newIntegerWindowFunc("NTILE", FixedLiteral(numOfBuckets))
|
||||
}
|
||||
|
||||
// LAG returns value evaluated at the row that is offset rows before the current row within the partition;
|
||||
// if there is no such row, instead return default (which must be of the same type as value).
|
||||
// Both offset and default are evaluated with respect to the current row.
|
||||
// If omitted, offset defaults to 1 and default to null
|
||||
func LAG(expr Expression, offsetAndDefault ...interface{}) windowExpression {
|
||||
return leadLagImpl("LAG", expr, offsetAndDefault...)
|
||||
}
|
||||
|
||||
// LEAD returns value evaluated at the row that is offset rows after the current row within the partition;
|
||||
// if there is no such row, instead return default (which must be of the same type as value).
|
||||
// Both offset and default are evaluated with respect to the current row.
|
||||
// If omitted, offset defaults to 1 and default to null
|
||||
func LEAD(expr Expression, offsetAndDefault ...interface{}) windowExpression {
|
||||
return leadLagImpl("LEAD", expr, offsetAndDefault...)
|
||||
}
|
||||
|
||||
// FIRST_VALUE returns value evaluated at the row that is the first row of the window frame
|
||||
func FIRST_VALUE(value Expression) windowExpression {
|
||||
return newWindowFunc("FIRST_VALUE", value)
|
||||
}
|
||||
|
||||
// LAST_VALUE returns value evaluated at the row that is the last row of the window frame
|
||||
func LAST_VALUE(value Expression) windowExpression {
|
||||
return newWindowFunc("LAST_VALUE", value)
|
||||
}
|
||||
|
||||
// NTH_VALUE returns value evaluated at the row that is the nth row of the window frame (counting from 1); null if no such row
|
||||
func NTH_VALUE(value Expression, nth int64) windowExpression {
|
||||
return newWindowFunc("NTH_VALUE", value, FixedLiteral(nth))
|
||||
}
|
||||
|
||||
func leadLagImpl(name string, expr Expression, offsetAndDefault ...interface{}) windowExpression {
|
||||
params := []Expression{expr}
|
||||
|
||||
if len(offsetAndDefault) >= 2 {
|
||||
offset, ok := offsetAndDefault[0].(int)
|
||||
if !ok {
|
||||
panic("jet: LAG offset should be an integer")
|
||||
}
|
||||
|
||||
var defaultValue Expression
|
||||
|
||||
defaultValue, ok = offsetAndDefault[1].(Expression)
|
||||
|
||||
if !ok {
|
||||
defaultValue = literal(offsetAndDefault[1])
|
||||
}
|
||||
|
||||
params = append(params, FixedLiteral(offset), defaultValue)
|
||||
}
|
||||
|
||||
return newWindowFunc(name, params...)
|
||||
}
|
||||
|
||||
//------------ String functions ------------------//
|
||||
|
||||
// BIT_LENGTH returns number of bits in string expression
|
||||
func BIT_LENGTH(stringExpression StringExpression) IntegerExpression {
|
||||
return newIntegerFunc("BIT_LENGTH", stringExpression)
|
||||
}
|
||||
|
||||
// CHAR_LENGTH returns number of characters in string expression
|
||||
func CHAR_LENGTH(stringExpression StringExpression) IntegerExpression {
|
||||
return newIntegerFunc("CHAR_LENGTH", stringExpression)
|
||||
}
|
||||
|
||||
// OCTET_LENGTH returns number of bytes in string expression
|
||||
func OCTET_LENGTH(stringExpression StringExpression) IntegerExpression {
|
||||
return newIntegerFunc("OCTET_LENGTH", stringExpression)
|
||||
}
|
||||
|
||||
// LOWER returns string expression in lower case
|
||||
func LOWER(stringExpression StringExpression) StringExpression {
|
||||
return NewStringFunc("LOWER", stringExpression)
|
||||
}
|
||||
|
||||
// UPPER returns string expression in upper case
|
||||
func UPPER(stringExpression StringExpression) StringExpression {
|
||||
return NewStringFunc("UPPER", stringExpression)
|
||||
}
|
||||
|
||||
// BTRIM removes the longest string consisting only of characters
|
||||
// in characters (a space by default) from the start and end of string
|
||||
func BTRIM(stringExpression StringExpression, trimChars ...StringExpression) StringExpression {
|
||||
if len(trimChars) > 0 {
|
||||
return NewStringFunc("BTRIM", stringExpression, trimChars[0])
|
||||
}
|
||||
return NewStringFunc("BTRIM", stringExpression)
|
||||
}
|
||||
|
||||
// LTRIM removes the longest string containing only characters
|
||||
// from characters (a space by default) from the start of string
|
||||
func LTRIM(str StringExpression, trimChars ...StringExpression) StringExpression {
|
||||
if len(trimChars) > 0 {
|
||||
return NewStringFunc("LTRIM", str, trimChars[0])
|
||||
}
|
||||
return NewStringFunc("LTRIM", str)
|
||||
}
|
||||
|
||||
// RTRIM removes the longest string containing only characters
|
||||
// from characters (a space by default) from the end of string
|
||||
func RTRIM(str StringExpression, trimChars ...StringExpression) StringExpression {
|
||||
if len(trimChars) > 0 {
|
||||
return NewStringFunc("RTRIM", str, trimChars[0])
|
||||
}
|
||||
return NewStringFunc("RTRIM", str)
|
||||
}
|
||||
|
||||
// CHR returns character with the given code.
|
||||
func CHR(integerExpression IntegerExpression) StringExpression {
|
||||
return NewStringFunc("CHR", integerExpression)
|
||||
}
|
||||
|
||||
// CONCAT adds two or more expressions together
|
||||
func CONCAT(expressions ...Expression) StringExpression {
|
||||
return NewStringFunc("CONCAT", expressions...)
|
||||
}
|
||||
|
||||
// CONCAT_WS adds two or more expressions together with a separator.
|
||||
func CONCAT_WS(separator Expression, expressions ...Expression) StringExpression {
|
||||
return NewStringFunc("CONCAT_WS", append([]Expression{separator}, expressions...)...)
|
||||
}
|
||||
|
||||
// CONVERT converts string to dest_encoding. The original encoding is
|
||||
// specified by src_encoding. The string must be valid in this encoding.
|
||||
func CONVERT(str StringExpression, srcEncoding StringExpression, destEncoding StringExpression) StringExpression {
|
||||
return NewStringFunc("CONVERT", str, srcEncoding, destEncoding)
|
||||
}
|
||||
|
||||
// CONVERT_FROM converts string to the database encoding. The original
|
||||
// encoding is specified by src_encoding. The string must be valid in this encoding.
|
||||
func CONVERT_FROM(str StringExpression, srcEncoding StringExpression) StringExpression {
|
||||
return NewStringFunc("CONVERT_FROM", str, srcEncoding)
|
||||
}
|
||||
|
||||
// CONVERT_TO converts string to dest_encoding.
|
||||
func CONVERT_TO(str StringExpression, toEncoding StringExpression) StringExpression {
|
||||
return NewStringFunc("CONVERT_TO", str, toEncoding)
|
||||
}
|
||||
|
||||
// ENCODE encodes binary data into a textual representation.
|
||||
// Supported formats are: base64, hex, escape. escape converts zero bytes and
|
||||
// high-bit-set bytes to octal sequences (\nnn) and doubles backslashes.
|
||||
func ENCODE(data StringExpression, format StringExpression) StringExpression {
|
||||
return NewStringFunc("ENCODE", data, format)
|
||||
}
|
||||
|
||||
// DECODE decodes binary data from textual representation in string.
|
||||
// Options for format are same as in encode.
|
||||
func DECODE(data StringExpression, format StringExpression) StringExpression {
|
||||
return NewStringFunc("DECODE", data, format)
|
||||
}
|
||||
|
||||
// FORMAT formats a number to a format like "#,###,###.##", rounded to a specified number of decimal places, then it returns the result as a string.
|
||||
func FORMAT(formatStr StringExpression, formatArgs ...Expression) StringExpression {
|
||||
args := []Expression{formatStr}
|
||||
args = append(args, formatArgs...)
|
||||
return NewStringFunc("FORMAT", args...)
|
||||
}
|
||||
|
||||
// INITCAP converts the first letter of each word to upper case
|
||||
// and the rest to lower case. Words are sequences of alphanumeric
|
||||
// characters separated by non-alphanumeric characters.
|
||||
func INITCAP(str StringExpression) StringExpression {
|
||||
return NewStringFunc("INITCAP", str)
|
||||
}
|
||||
|
||||
// LEFT returns first n characters in the string.
|
||||
// When n is negative, return all but last |n| characters.
|
||||
func LEFT(str StringExpression, n IntegerExpression) StringExpression {
|
||||
return NewStringFunc("LEFT", str, n)
|
||||
}
|
||||
|
||||
// RIGHT returns last n characters in the string.
|
||||
// When n is negative, return all but first |n| characters.
|
||||
func RIGHT(str StringExpression, n IntegerExpression) StringExpression {
|
||||
return NewStringFunc("RIGHT", str, n)
|
||||
}
|
||||
|
||||
// LENGTH returns number of characters in string with a given encoding
|
||||
func LENGTH(str StringExpression, encoding ...StringExpression) StringExpression {
|
||||
if len(encoding) > 0 {
|
||||
return NewStringFunc("LENGTH", str, encoding[0])
|
||||
}
|
||||
return NewStringFunc("LENGTH", str)
|
||||
}
|
||||
|
||||
// LPAD fills up the string to length length by prepending the characters
|
||||
// fill (a space by default). If the string is already longer than length
|
||||
// then it is truncated (on the right).
|
||||
func LPAD(str StringExpression, length IntegerExpression, text ...StringExpression) StringExpression {
|
||||
if len(text) > 0 {
|
||||
return NewStringFunc("LPAD", str, length, text[0])
|
||||
}
|
||||
|
||||
return NewStringFunc("LPAD", str, length)
|
||||
}
|
||||
|
||||
// RPAD fills up the string to length length by appending the characters
|
||||
// fill (a space by default). If the string is already longer than length then it is truncated.
|
||||
func RPAD(str StringExpression, length IntegerExpression, text ...StringExpression) StringExpression {
|
||||
if len(text) > 0 {
|
||||
return NewStringFunc("RPAD", str, length, text[0])
|
||||
}
|
||||
|
||||
return NewStringFunc("RPAD", str, length)
|
||||
}
|
||||
|
||||
// MD5 calculates the MD5 hash of string, returning the result in hexadecimal
|
||||
func MD5(stringExpression StringExpression) StringExpression {
|
||||
return NewStringFunc("MD5", stringExpression)
|
||||
}
|
||||
|
||||
// REPEAT repeats string the specified number of times
|
||||
func REPEAT(str StringExpression, n IntegerExpression) StringExpression {
|
||||
return NewStringFunc("REPEAT", str, n)
|
||||
}
|
||||
|
||||
// REPLACE replaces all occurrences in string of substring from with substring to
|
||||
func REPLACE(text, from, to StringExpression) StringExpression {
|
||||
return NewStringFunc("REPLACE", text, from, to)
|
||||
}
|
||||
|
||||
// REVERSE returns reversed string.
|
||||
func REVERSE(stringExpression StringExpression) StringExpression {
|
||||
return NewStringFunc("REVERSE", stringExpression)
|
||||
}
|
||||
|
||||
// STRPOS returns location of specified substring (same as position(substring in string),
|
||||
// but note the reversed argument order)
|
||||
func STRPOS(str, substring StringExpression) IntegerExpression {
|
||||
return newIntegerFunc("STRPOS", str, substring)
|
||||
}
|
||||
|
||||
// SUBSTR extracts substring
|
||||
func SUBSTR(str StringExpression, from IntegerExpression, count ...IntegerExpression) StringExpression {
|
||||
if len(count) > 0 {
|
||||
return NewStringFunc("SUBSTR", str, from, count[0])
|
||||
}
|
||||
return NewStringFunc("SUBSTR", str, from)
|
||||
}
|
||||
|
||||
// TO_ASCII convert string to ASCII from another encoding
|
||||
func TO_ASCII(str StringExpression, encoding ...StringExpression) StringExpression {
|
||||
if len(encoding) > 0 {
|
||||
return NewStringFunc("TO_ASCII", str, encoding[0])
|
||||
}
|
||||
return NewStringFunc("TO_ASCII", str)
|
||||
}
|
||||
|
||||
// TO_HEX converts number to its equivalent hexadecimal representation
|
||||
func TO_HEX(number IntegerExpression) StringExpression {
|
||||
return NewStringFunc("TO_HEX", number)
|
||||
}
|
||||
|
||||
// REGEXP_LIKE Returns 1 if the string expr matches the regular expression specified by the pattern pat, 0 otherwise.
|
||||
func REGEXP_LIKE(stringExp StringExpression, pattern StringExpression, matchType ...string) BoolExpression {
|
||||
if len(matchType) > 0 {
|
||||
return newBoolFunc("REGEXP_LIKE", stringExp, pattern, FixedLiteral(matchType[0]))
|
||||
}
|
||||
|
||||
return newBoolFunc("REGEXP_LIKE", stringExp, pattern)
|
||||
}
|
||||
|
||||
//----------Range Type Functions ----------------------//
|
||||
|
||||
// LOWER_BOUND returns range expressions lower bound. Returns null if range is empty or the requested bound is infinite.
|
||||
func LOWER_BOUND[T Expression](rangeExpression Range[T]) T {
|
||||
return rangeTypeCaster[T](rangeExpression, NewFunc("LOWER", []Expression{rangeExpression}, nil))
|
||||
}
|
||||
|
||||
// UPPER_BOUND returns range expressions upper bound. Returns null if range is empty or the requested bound is infinite.
|
||||
func UPPER_BOUND[T Expression](rangeExpression Range[T]) T {
|
||||
return rangeTypeCaster[T](rangeExpression, NewFunc("UPPER", []Expression{rangeExpression}, nil))
|
||||
}
|
||||
|
||||
func rangeTypeCaster[T Expression](rangeExpression Range[T], exp Expression) T {
|
||||
var i Expression
|
||||
switch rangeExpression.(type) {
|
||||
case Range[Int4Expression], Range[Int8Expression]:
|
||||
i = IntExp(exp)
|
||||
case Range[NumericExpression]:
|
||||
i = FloatExp(exp)
|
||||
case Range[DateExpression]:
|
||||
i = DateExp(exp)
|
||||
case Range[TimestampExpression]:
|
||||
i = TimestampExp(exp)
|
||||
case Range[TimestampzExpression]:
|
||||
i = TimestampzExp(exp)
|
||||
}
|
||||
return i.(T)
|
||||
}
|
||||
|
||||
// IS_EMPTY returns true if range is empty
|
||||
func IS_EMPTY[T Expression](rangeExpression Range[T]) BoolExpression {
|
||||
return newBoolFunc("ISEMPTY", rangeExpression)
|
||||
}
|
||||
|
||||
// LOWER_INC returns true if lower bound is inclusive. Returns false for empty range.
|
||||
func LOWER_INC[T Expression](rangeExpression Range[T]) BoolExpression {
|
||||
return newBoolFunc("LOWER_INC", rangeExpression)
|
||||
}
|
||||
|
||||
// UPPER_INC returns true if upper bound is inclusive. Returns false for empty range.
|
||||
func UPPER_INC[T Expression](rangeExpression Range[T]) BoolExpression {
|
||||
return newBoolFunc("UPPER_INC", rangeExpression)
|
||||
}
|
||||
|
||||
// LOWER_INF returns true if upper bound is infinite. Returns false for empty range.
|
||||
func LOWER_INF[T Expression](rangeExpression Range[T]) BoolExpression {
|
||||
return newBoolFunc("LOWER_INF", rangeExpression)
|
||||
}
|
||||
|
||||
// UPPER_INF returns true if lower bound is infinite. Returns false for empty range.
|
||||
func UPPER_INF[T Expression](rangeExpression Range[T]) BoolExpression {
|
||||
return newBoolFunc("UPPER_INF", rangeExpression)
|
||||
}
|
||||
|
||||
//----------Data Type Formatting Functions ----------------------//
|
||||
|
||||
// TO_CHAR converts expression to string with format
|
||||
func TO_CHAR(expression Expression, format StringExpression) StringExpression {
|
||||
return NewStringFunc("TO_CHAR", expression, format)
|
||||
}
|
||||
|
||||
// TO_DATE converts string to date using format
|
||||
func TO_DATE(dateStr, format StringExpression) DateExpression {
|
||||
return NewDateFunc("TO_DATE", dateStr, format)
|
||||
}
|
||||
|
||||
// TO_NUMBER converts string to numeric using format
|
||||
func TO_NUMBER(floatStr, format StringExpression) FloatExpression {
|
||||
return NewFloatFunc("TO_NUMBER", floatStr, format)
|
||||
}
|
||||
|
||||
// TO_TIMESTAMP converts string to time stamp with time zone using format
|
||||
func TO_TIMESTAMP(timestampzStr, format StringExpression) TimestampzExpression {
|
||||
return newTimestampzFunc("TO_TIMESTAMP", timestampzStr, format)
|
||||
}
|
||||
|
||||
//----------------- Date/Time Functions and Operators ---------------//
|
||||
|
||||
// EXTRACT extracts time component from time expression
|
||||
func EXTRACT(field string, from Expression) Expression {
|
||||
return CustomExpression(Token("EXTRACT("), Token(field), Token("FROM"), from, Token(")"))
|
||||
}
|
||||
|
||||
// CURRENT_DATE returns current date
|
||||
func CURRENT_DATE() DateExpression {
|
||||
dateFunc := NewDateFunc("CURRENT_DATE")
|
||||
dateFunc.noBrackets = true
|
||||
return dateFunc
|
||||
}
|
||||
|
||||
// CURRENT_TIME returns current time with time zone
|
||||
func CURRENT_TIME(precision ...int) TimezExpression {
|
||||
var timezFunc *timezFunc
|
||||
|
||||
if len(precision) > 0 {
|
||||
timezFunc = newTimezFunc("CURRENT_TIME", FixedLiteral(precision[0]))
|
||||
} else {
|
||||
timezFunc = newTimezFunc("CURRENT_TIME")
|
||||
}
|
||||
|
||||
timezFunc.noBrackets = true
|
||||
|
||||
return timezFunc
|
||||
}
|
||||
|
||||
// CURRENT_TIMESTAMP returns current timestamp with time zone
|
||||
func CURRENT_TIMESTAMP(precision ...int) TimestampzExpression {
|
||||
var timestampzFunc *timestampzFunc
|
||||
|
||||
if len(precision) > 0 {
|
||||
timestampzFunc = newTimestampzFunc("CURRENT_TIMESTAMP", FixedLiteral(precision[0]))
|
||||
} else {
|
||||
timestampzFunc = newTimestampzFunc("CURRENT_TIMESTAMP")
|
||||
}
|
||||
|
||||
timestampzFunc.noBrackets = true
|
||||
|
||||
return timestampzFunc
|
||||
}
|
||||
|
||||
// LOCALTIME returns local time of day using optional precision
|
||||
func LOCALTIME(precision ...int) TimeExpression {
|
||||
var timeFunc *timeFunc
|
||||
|
||||
if len(precision) > 0 {
|
||||
timeFunc = NewTimeFunc("LOCALTIME", FixedLiteral(precision[0]))
|
||||
} else {
|
||||
timeFunc = NewTimeFunc("LOCALTIME")
|
||||
}
|
||||
|
||||
timeFunc.noBrackets = true
|
||||
|
||||
return timeFunc
|
||||
}
|
||||
|
||||
// LOCALTIMESTAMP returns current date and time using optional precision
|
||||
func LOCALTIMESTAMP(precision ...int) TimestampExpression {
|
||||
var timestampFunc *timestampFunc
|
||||
|
||||
if len(precision) > 0 {
|
||||
timestampFunc = NewTimestampFunc("LOCALTIMESTAMP", FixedLiteral(precision[0]))
|
||||
} else {
|
||||
timestampFunc = NewTimestampFunc("LOCALTIMESTAMP")
|
||||
}
|
||||
|
||||
timestampFunc.noBrackets = true
|
||||
|
||||
return timestampFunc
|
||||
}
|
||||
|
||||
// NOW returns current date and time
|
||||
func NOW() TimestampzExpression {
|
||||
return newTimestampzFunc("NOW")
|
||||
}
|
||||
|
||||
// --------------- Conditional Expressions Functions -------------//
|
||||
|
||||
// COALESCE function returns the first of its arguments that is not null.
|
||||
func COALESCE(value Expression, values ...Expression) Expression {
|
||||
var allValues = []Expression{value}
|
||||
allValues = append(allValues, values...)
|
||||
return NewFunc("COALESCE", allValues, nil)
|
||||
}
|
||||
|
||||
// NULLIF function returns a null value if value1 equals value2; otherwise it returns value1.
|
||||
func NULLIF(value1, value2 Expression) Expression {
|
||||
return NewFunc("NULLIF", []Expression{value1, value2}, nil)
|
||||
}
|
||||
|
||||
// GREATEST selects the largest value from a list of expressions
|
||||
func GREATEST(value Expression, values ...Expression) Expression {
|
||||
var allValues = []Expression{value}
|
||||
allValues = append(allValues, values...)
|
||||
return NewFunc("GREATEST", allValues, nil)
|
||||
}
|
||||
|
||||
// LEAST selects the smallest value from a list of expressions
|
||||
func LEAST(value Expression, values ...Expression) Expression {
|
||||
var allValues = []Expression{value}
|
||||
allValues = append(allValues, values...)
|
||||
return NewFunc("LEAST", allValues, nil)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------//
|
||||
|
||||
type funcExpressionImpl struct {
|
||||
ExpressionInterfaceImpl
|
||||
|
||||
name string
|
||||
parameters parametersSerializer
|
||||
noBrackets bool
|
||||
}
|
||||
|
||||
// NewFunc creates new function with name and expressions parameters
|
||||
func NewFunc(name string, expressions []Expression, parent Expression) *funcExpressionImpl {
|
||||
funcExp := &funcExpressionImpl{
|
||||
name: name,
|
||||
parameters: parametersSerializer(expressions),
|
||||
}
|
||||
|
||||
if parent != nil {
|
||||
funcExp.ExpressionInterfaceImpl.Parent = parent
|
||||
} else {
|
||||
funcExp.ExpressionInterfaceImpl.Parent = funcExp
|
||||
}
|
||||
|
||||
return funcExp
|
||||
}
|
||||
|
||||
func (f *funcExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if serializeOverride := out.Dialect.FunctionSerializeOverride(f.name); serializeOverride != nil {
|
||||
serializeOverrideFunc := serializeOverride(ExpressionListToSerializerList(f.parameters)...)
|
||||
serializeOverrideFunc(statement, out, FallTrough(options)...)
|
||||
return
|
||||
}
|
||||
|
||||
addBrackets := !f.noBrackets || len(f.parameters) > 0
|
||||
|
||||
if addBrackets {
|
||||
out.WriteString(f.name + "(")
|
||||
} else {
|
||||
out.WriteString(f.name)
|
||||
}
|
||||
|
||||
f.parameters.serialize(statement, out, options...)
|
||||
|
||||
if addBrackets {
|
||||
out.WriteString(")")
|
||||
}
|
||||
}
|
||||
|
||||
type parametersSerializer []Expression
|
||||
|
||||
func (p parametersSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
|
||||
for i, expression := range p {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
|
||||
if _, isStatement := expression.(Statement); isStatement {
|
||||
expression.serialize(statement, out, options...)
|
||||
} else {
|
||||
expression.serialize(statement, out, append(options, NoWrap, Ident)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewFloatWindowFunc creates new float function with name and expressions
|
||||
func newWindowFunc(name string, expressions ...Expression) windowExpression {
|
||||
newFun := NewFunc(name, expressions, nil)
|
||||
windowExpr := newWindowExpression(newFun)
|
||||
newFun.ExpressionInterfaceImpl.Parent = windowExpr
|
||||
|
||||
return windowExpr
|
||||
}
|
||||
|
||||
type boolFunc struct {
|
||||
funcExpressionImpl
|
||||
boolInterfaceImpl
|
||||
}
|
||||
|
||||
func newBoolFunc(name string, expressions ...Expression) BoolExpression {
|
||||
boolFunc := &boolFunc{}
|
||||
|
||||
boolFunc.funcExpressionImpl = *NewFunc(name, expressions, boolFunc)
|
||||
boolFunc.boolInterfaceImpl.parent = boolFunc
|
||||
boolFunc.ExpressionInterfaceImpl.Parent = boolFunc
|
||||
|
||||
return boolFunc
|
||||
}
|
||||
|
||||
// NewFloatWindowFunc creates new float function with name and expressions
|
||||
func newBoolWindowFunc(name string, expressions ...Expression) boolWindowExpression {
|
||||
boolFunc := &boolFunc{}
|
||||
|
||||
boolFunc.funcExpressionImpl = *NewFunc(name, expressions, boolFunc)
|
||||
intWindowFunc := newBoolWindowExpression(boolFunc)
|
||||
boolFunc.boolInterfaceImpl.parent = intWindowFunc
|
||||
boolFunc.ExpressionInterfaceImpl.Parent = intWindowFunc
|
||||
|
||||
return intWindowFunc
|
||||
}
|
||||
|
||||
type floatFunc struct {
|
||||
funcExpressionImpl
|
||||
floatInterfaceImpl
|
||||
}
|
||||
|
||||
// NewFloatFunc creates new float function with name and expressions
|
||||
func NewFloatFunc(name string, expressions ...Expression) FloatExpression {
|
||||
floatFunc := &floatFunc{}
|
||||
|
||||
floatFunc.funcExpressionImpl = *NewFunc(name, expressions, floatFunc)
|
||||
floatFunc.floatInterfaceImpl.parent = floatFunc
|
||||
|
||||
return floatFunc
|
||||
}
|
||||
|
||||
// NewFloatWindowFunc creates new float function with name and expressions
|
||||
func NewFloatWindowFunc(name string, expressions ...Expression) floatWindowExpression {
|
||||
floatFunc := &floatFunc{}
|
||||
|
||||
floatFunc.funcExpressionImpl = *NewFunc(name, expressions, floatFunc)
|
||||
floatWindowFunc := newFloatWindowExpression(floatFunc)
|
||||
floatFunc.floatInterfaceImpl.parent = floatWindowFunc
|
||||
floatFunc.ExpressionInterfaceImpl.Parent = floatWindowFunc
|
||||
|
||||
return floatWindowFunc
|
||||
}
|
||||
|
||||
type integerFunc struct {
|
||||
funcExpressionImpl
|
||||
integerInterfaceImpl
|
||||
}
|
||||
|
||||
func newIntegerFunc(name string, expressions ...Expression) IntegerExpression {
|
||||
intFunc := &integerFunc{}
|
||||
|
||||
intFunc.funcExpressionImpl = *NewFunc(name, expressions, intFunc)
|
||||
intFunc.integerInterfaceImpl.parent = intFunc
|
||||
|
||||
return intFunc
|
||||
}
|
||||
|
||||
// NewFloatWindowFunc creates new float function with name and expressions
|
||||
func newIntegerWindowFunc(name string, expressions ...Expression) integerWindowExpression {
|
||||
integerFunc := &integerFunc{}
|
||||
|
||||
integerFunc.funcExpressionImpl = *NewFunc(name, expressions, integerFunc)
|
||||
intWindowFunc := newIntegerWindowExpression(integerFunc)
|
||||
integerFunc.integerInterfaceImpl.parent = intWindowFunc
|
||||
integerFunc.ExpressionInterfaceImpl.Parent = intWindowFunc
|
||||
|
||||
return intWindowFunc
|
||||
}
|
||||
|
||||
type stringFunc struct {
|
||||
funcExpressionImpl
|
||||
stringInterfaceImpl
|
||||
}
|
||||
|
||||
// NewStringFunc creates new string function with name and expression parameters
|
||||
func NewStringFunc(name string, expressions ...Expression) StringExpression {
|
||||
stringFunc := &stringFunc{}
|
||||
|
||||
stringFunc.funcExpressionImpl = *NewFunc(name, expressions, stringFunc)
|
||||
stringFunc.stringInterfaceImpl.parent = stringFunc
|
||||
|
||||
return stringFunc
|
||||
}
|
||||
|
||||
type dateFunc struct {
|
||||
funcExpressionImpl
|
||||
dateInterfaceImpl
|
||||
}
|
||||
|
||||
// NewDateFunc creates new date function with name and expression parameters
|
||||
func NewDateFunc(name string, expressions ...Expression) *dateFunc {
|
||||
dateFunc := &dateFunc{}
|
||||
|
||||
dateFunc.funcExpressionImpl = *NewFunc(name, expressions, dateFunc)
|
||||
dateFunc.dateInterfaceImpl.parent = dateFunc
|
||||
|
||||
return dateFunc
|
||||
}
|
||||
|
||||
type timeFunc struct {
|
||||
funcExpressionImpl
|
||||
timeInterfaceImpl
|
||||
}
|
||||
|
||||
// NewTimeFunc creates new time function with name and expression parameters
|
||||
func NewTimeFunc(name string, expressions ...Expression) *timeFunc {
|
||||
timeFun := &timeFunc{}
|
||||
|
||||
timeFun.funcExpressionImpl = *NewFunc(name, expressions, timeFun)
|
||||
timeFun.timeInterfaceImpl.parent = timeFun
|
||||
|
||||
return timeFun
|
||||
}
|
||||
|
||||
type timezFunc struct {
|
||||
funcExpressionImpl
|
||||
timezInterfaceImpl
|
||||
}
|
||||
|
||||
func newTimezFunc(name string, expressions ...Expression) *timezFunc {
|
||||
timezFun := &timezFunc{}
|
||||
|
||||
timezFun.funcExpressionImpl = *NewFunc(name, expressions, timezFun)
|
||||
timezFun.timezInterfaceImpl.parent = timezFun
|
||||
|
||||
return timezFun
|
||||
}
|
||||
|
||||
type timestampFunc struct {
|
||||
funcExpressionImpl
|
||||
timestampInterfaceImpl
|
||||
}
|
||||
|
||||
// NewTimestampFunc creates new timestamp function with name and expressions
|
||||
func NewTimestampFunc(name string, expressions ...Expression) *timestampFunc {
|
||||
timestampFunc := ×tampFunc{}
|
||||
|
||||
timestampFunc.funcExpressionImpl = *NewFunc(name, expressions, timestampFunc)
|
||||
timestampFunc.timestampInterfaceImpl.parent = timestampFunc
|
||||
|
||||
return timestampFunc
|
||||
}
|
||||
|
||||
type timestampzFunc struct {
|
||||
funcExpressionImpl
|
||||
timestampzInterfaceImpl
|
||||
}
|
||||
|
||||
func newTimestampzFunc(name string, expressions ...Expression) *timestampzFunc {
|
||||
timestampzFunc := ×tampzFunc{}
|
||||
|
||||
timestampzFunc.funcExpressionImpl = *NewFunc(name, expressions, timestampzFunc)
|
||||
timestampzFunc.timestampzInterfaceImpl.parent = timestampzFunc
|
||||
|
||||
return timestampzFunc
|
||||
}
|
||||
|
||||
// Func can be used to call custom or unsupported database functions.
|
||||
func Func(name string, expressions ...Expression) Expression {
|
||||
return NewFunc(name, expressions, nil)
|
||||
}
|
||||
|
||||
func NumRange(lowNum, highNum NumericExpression, bounds ...StringExpression) Range[NumericExpression] {
|
||||
return NumRangeExp(NewFunc("numrange", rangeFuncParamCombiner(lowNum, highNum, bounds...), nil))
|
||||
}
|
||||
|
||||
func Int4Range(lowNum, highNum IntegerExpression, bounds ...StringExpression) Range[Int4Expression] {
|
||||
return Int4RangeExp(NewFunc("int4range", rangeFuncParamCombiner(lowNum, highNum, bounds...), nil))
|
||||
}
|
||||
|
||||
func Int8Range(lowNum, highNum Int8Expression, bounds ...StringExpression) Range[Int8Expression] {
|
||||
return Int8RangeExp(NewFunc("int8range", rangeFuncParamCombiner(lowNum, highNum, bounds...), nil))
|
||||
}
|
||||
|
||||
func TsRange(lowTs, highTs TimestampExpression, bounds ...StringExpression) Range[TimestampExpression] {
|
||||
return TsRangeExp(NewFunc("tsrange", rangeFuncParamCombiner(lowTs, highTs, bounds...), nil))
|
||||
}
|
||||
|
||||
func TstzRange(lowTs, highTs TimestampzExpression, bounds ...StringExpression) Range[TimestampzExpression] {
|
||||
return TstzRangeExp(NewFunc("tstzrange", rangeFuncParamCombiner(lowTs, highTs, bounds...), nil))
|
||||
}
|
||||
|
||||
func DateRange(lowTs, highTs DateExpression, bounds ...StringExpression) Range[DateExpression] {
|
||||
return DateRangeExp(NewFunc("daterange", rangeFuncParamCombiner(lowTs, highTs, bounds...), nil))
|
||||
}
|
||||
|
||||
func rangeFuncParamCombiner(low, high Expression, bounds ...StringExpression) []Expression {
|
||||
exp := []Expression{low, high}
|
||||
if len(bounds) != 0 {
|
||||
exp = append(exp, bounds[0])
|
||||
}
|
||||
return exp
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAND(t *testing.T) {
|
||||
assertClauseSerializeErr(t, AND(), "jet: syntax error, expression list empty")
|
||||
assertClauseSerialize(t, AND(table1ColInt.IS_NULL()), `table1.col_int IS NULL`) // IS NULL doesn't add parenthesis
|
||||
assertClauseSerialize(t, AND(table1ColInt.LT(Int(11))), `(table1.col_int < $1)`, int64(11))
|
||||
assertClauseSerialize(t, AND(table1ColInt.GT(Int(11)), table1ColFloat.EQ(Float(0))),
|
||||
`(
|
||||
(table1.col_int > $1)
|
||||
AND (table1.col_float = $2)
|
||||
)`, int64(11), 0.0)
|
||||
}
|
||||
|
||||
func TestOR(t *testing.T) {
|
||||
assertClauseSerializeErr(t, OR(), "jet: syntax error, expression list empty")
|
||||
assertClauseSerialize(t, OR(table1ColInt.IS_NULL()), `table1.col_int IS NULL`) // IS NULL doesn't add parenthesis
|
||||
assertClauseSerialize(t, OR(table1ColInt.LT(Int(11))), `(table1.col_int < $1)`, int64(11))
|
||||
assertClauseSerialize(t, OR(table1ColInt.GT(Int(11)), table1ColFloat.EQ(Float(0))),
|
||||
`(
|
||||
(table1.col_int > $1)
|
||||
OR (table1.col_float = $2)
|
||||
)`, int64(11), 0.0)
|
||||
}
|
||||
|
||||
func TestFuncAVG(t *testing.T) {
|
||||
assertClauseSerialize(t, AVG(table1ColFloat), "AVG(table1.col_float)")
|
||||
assertClauseSerialize(t, AVG(table1ColInt), "AVG(table1.col_int)")
|
||||
}
|
||||
|
||||
func TestFuncBIT_AND(t *testing.T) {
|
||||
assertClauseSerialize(t, BIT_AND(table1ColInt), "BIT_AND(table1.col_int)")
|
||||
}
|
||||
|
||||
func TestFuncBIT_OR(t *testing.T) {
|
||||
assertClauseSerialize(t, BIT_OR(table1ColInt), "BIT_OR(table1.col_int)")
|
||||
}
|
||||
|
||||
func TestFuncBOOL_AND(t *testing.T) {
|
||||
assertClauseSerialize(t, BOOL_AND(table1ColBool), "BOOL_AND(table1.col_bool)")
|
||||
}
|
||||
|
||||
func TestFuncBOOL_OR(t *testing.T) {
|
||||
assertClauseSerialize(t, BOOL_OR(table1ColBool), "BOOL_OR(table1.col_bool)")
|
||||
}
|
||||
|
||||
func TestFuncEVERY(t *testing.T) {
|
||||
assertClauseSerialize(t, EVERY(table1ColBool), "EVERY(table1.col_bool)")
|
||||
}
|
||||
|
||||
func TestFuncMIN(t *testing.T) {
|
||||
t.Run("expression", func(t *testing.T) {
|
||||
assertClauseSerialize(t, MIN(table1ColDate), "MIN(table1.col_date)")
|
||||
assertClauseSerialize(t, MIN(Date(2001, 1, 1)), "MIN($1)", "2001-01-01")
|
||||
assertClauseSerialize(t, MIN(Time(12, 10, 10)), "MIN($1)", "12:10:10")
|
||||
assertClauseSerialize(t, MIN(Timestamp(2001, 1, 1, 12, 10, 10)), "MIN($1)", "2001-01-01 12:10:10")
|
||||
assertClauseSerialize(t, MIN(Timestampz(2001, 1, 1, 12, 10, 10, 1, "UTC")), "MIN($1)", "2001-01-01 12:10:10.000000001 UTC")
|
||||
})
|
||||
|
||||
t.Run("float", func(t *testing.T) {
|
||||
assertClauseSerialize(t, MINf(table1ColFloat), "MIN(table1.col_float)")
|
||||
})
|
||||
|
||||
t.Run("integer", func(t *testing.T) {
|
||||
assertClauseSerialize(t, MINi(table1ColInt), "MIN(table1.col_int)")
|
||||
})
|
||||
}
|
||||
|
||||
func TestFuncMAX(t *testing.T) {
|
||||
t.Run("expression", func(t *testing.T) {
|
||||
assertClauseSerialize(t, MAX(table1ColDate), "MAX(table1.col_date)")
|
||||
assertClauseSerialize(t, MAX(Date(2001, 1, 1)), "MAX($1)", "2001-01-01")
|
||||
assertClauseSerialize(t, MAX(Time(12, 10, 10)), "MAX($1)", "12:10:10")
|
||||
assertClauseSerialize(t, MAX(Timestamp(2001, 1, 1, 12, 10, 10)), "MAX($1)", "2001-01-01 12:10:10")
|
||||
assertClauseSerialize(t, MAX(Timestampz(2001, 1, 1, 12, 10, 10, 1, "UTC")), "MAX($1)", "2001-01-01 12:10:10.000000001 UTC")
|
||||
})
|
||||
|
||||
t.Run("float", func(t *testing.T) {
|
||||
assertClauseSerialize(t, MAXf(table1ColFloat), "MAX(table1.col_float)")
|
||||
assertClauseSerialize(t, MAXf(Float(11.2222)), "MAX($1)", float64(11.2222))
|
||||
})
|
||||
|
||||
t.Run("integer", func(t *testing.T) {
|
||||
assertClauseSerialize(t, MAXi(table1ColInt), "MAX(table1.col_int)")
|
||||
assertClauseSerialize(t, MAXi(Int(11)), "MAX($1)", int64(11))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFuncSUM(t *testing.T) {
|
||||
t.Run("float", func(t *testing.T) {
|
||||
assertClauseSerialize(t, SUMf(table1ColFloat), "SUM(table1.col_float)")
|
||||
assertClauseSerialize(t, SUMf(Float(11.2222)), "SUM($1)", float64(11.2222))
|
||||
})
|
||||
|
||||
t.Run("integer", func(t *testing.T) {
|
||||
assertClauseSerialize(t, SUMi(table1ColInt), "SUM(table1.col_int)")
|
||||
assertClauseSerialize(t, SUMi(Int(11)), "SUM($1)", int64(11))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFuncCOUNT(t *testing.T) {
|
||||
assertClauseSerialize(t, COUNT(STAR), "COUNT(*)")
|
||||
assertClauseSerialize(t, COUNT(table1ColFloat), "COUNT(table1.col_float)")
|
||||
assertClauseSerialize(t, COUNT(Float(11.2222)), "COUNT($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncABS(t *testing.T) {
|
||||
t.Run("float", func(t *testing.T) {
|
||||
assertClauseSerialize(t, ABSf(table1ColFloat), "ABS(table1.col_float)")
|
||||
assertClauseSerialize(t, ABSf(Float(11.2222)), "ABS($1)", float64(11.2222))
|
||||
})
|
||||
|
||||
t.Run("integer", func(t *testing.T) {
|
||||
assertClauseSerialize(t, ABSi(table1ColInt), "ABS(table1.col_int)")
|
||||
assertClauseSerialize(t, ABSi(Int(11)), "ABS($1)", int64(11))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFuncSQRT(t *testing.T) {
|
||||
assertClauseSerialize(t, SQRT(table1ColFloat), "SQRT(table1.col_float)")
|
||||
assertClauseSerialize(t, SQRT(Float(11.2222)), "SQRT($1)", float64(11.2222))
|
||||
assertClauseSerialize(t, SQRT(table1ColInt), "SQRT(table1.col_int)")
|
||||
assertClauseSerialize(t, SQRT(Int(11)), "SQRT($1)", int64(11))
|
||||
}
|
||||
|
||||
func TestFuncCBRT(t *testing.T) {
|
||||
assertClauseSerialize(t, CBRT(table1ColFloat), "CBRT(table1.col_float)")
|
||||
assertClauseSerialize(t, CBRT(Float(11.2222)), "CBRT($1)", float64(11.2222))
|
||||
assertClauseSerialize(t, CBRT(table1ColInt), "CBRT(table1.col_int)")
|
||||
assertClauseSerialize(t, CBRT(Int(11)), "CBRT($1)", int64(11))
|
||||
}
|
||||
|
||||
func TestFuncCEIL(t *testing.T) {
|
||||
assertClauseSerialize(t, CEIL(table1ColFloat), "CEIL(table1.col_float)")
|
||||
assertClauseSerialize(t, CEIL(Float(11.2222)), "CEIL($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncFLOOR(t *testing.T) {
|
||||
assertClauseSerialize(t, FLOOR(table1ColFloat), "FLOOR(table1.col_float)")
|
||||
assertClauseSerialize(t, FLOOR(Float(11.2222)), "FLOOR($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncROUND(t *testing.T) {
|
||||
assertClauseSerialize(t, ROUND(table1ColFloat), "ROUND(table1.col_float)")
|
||||
assertClauseSerialize(t, ROUND(Float(11.2222)), "ROUND($1)", float64(11.2222))
|
||||
|
||||
assertClauseSerialize(t, ROUND(table1ColFloat, Int(2)), "ROUND(table1.col_float, $1)", int64(2))
|
||||
assertClauseSerialize(t, ROUND(Float(11.2222), Int(1)), "ROUND($1, $2)", float64(11.2222), int64(1))
|
||||
}
|
||||
|
||||
func TestFuncSIGN(t *testing.T) {
|
||||
assertClauseSerialize(t, SIGN(table1ColFloat), "SIGN(table1.col_float)")
|
||||
assertClauseSerialize(t, SIGN(Float(11.2222)), "SIGN($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncTRUNC(t *testing.T) {
|
||||
assertClauseSerialize(t, TRUNC(table1ColFloat), "TRUNC(table1.col_float)")
|
||||
assertClauseSerialize(t, TRUNC(Float(11.2222)), "TRUNC($1)", float64(11.2222))
|
||||
|
||||
assertClauseSerialize(t, TRUNC(table1ColFloat, Int(2)), "TRUNC(table1.col_float, $1)", int64(2))
|
||||
assertClauseSerialize(t, TRUNC(Float(11.2222), Int(1)), "TRUNC($1, $2)", float64(11.2222), int64(1))
|
||||
}
|
||||
|
||||
func TestFuncLN(t *testing.T) {
|
||||
assertClauseSerialize(t, LN(table1ColFloat), "LN(table1.col_float)")
|
||||
assertClauseSerialize(t, LN(Float(11.2222)), "LN($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncLOG(t *testing.T) {
|
||||
assertClauseSerialize(t, LOG(table1ColFloat), "LOG(table1.col_float)")
|
||||
assertClauseSerialize(t, LOG(Float(11.2222)), "LOG($1)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncCOALESCE(t *testing.T) {
|
||||
assertClauseSerialize(t, COALESCE(table1ColFloat), "COALESCE(table1.col_float)")
|
||||
assertClauseSerialize(t, COALESCE(Float(11.2222), NULL, String("str")), "COALESCE($1, NULL, $2)", float64(11.2222), "str")
|
||||
}
|
||||
|
||||
func TestFuncNULLIF(t *testing.T) {
|
||||
assertClauseSerialize(t, NULLIF(table1ColFloat, table2ColInt), "NULLIF(table1.col_float, table2.col_int)")
|
||||
assertClauseSerialize(t, NULLIF(Float(11.2222), NULL), "NULLIF($1, NULL)", float64(11.2222))
|
||||
}
|
||||
|
||||
func TestFuncGREATEST(t *testing.T) {
|
||||
assertClauseSerialize(t, GREATEST(table1ColFloat), "GREATEST(table1.col_float)")
|
||||
assertClauseSerialize(t, GREATEST(Float(11.2222), NULL, String("str")), "GREATEST($1, NULL, $2)", float64(11.2222), "str")
|
||||
}
|
||||
|
||||
func TestFuncLEAST(t *testing.T) {
|
||||
assertClauseSerialize(t, LEAST(table1ColFloat), "LEAST(table1.col_float)")
|
||||
assertClauseSerialize(t, LEAST(Float(11.2222), NULL, String("str")), "LEAST($1, NULL, $2)", float64(11.2222), "str")
|
||||
}
|
||||
|
||||
func TestTO_ASCII(t *testing.T) {
|
||||
assertClauseSerialize(t, TO_ASCII(String("Karel")), `TO_ASCII($1)`, "Karel")
|
||||
assertClauseSerialize(t, TO_ASCII(String("Karel")), `TO_ASCII($1)`, "Karel")
|
||||
}
|
||||
|
||||
func TestFunc(t *testing.T) {
|
||||
assertClauseSerialize(t, Func("FOO", String("test"), NULL, MAX(Int(1))), "FOO($1, NULL, MAX($2))", "test", int64(1))
|
||||
}
|
||||
|
||||
func Test_rangePointCaster(t *testing.T) {
|
||||
mainRange := Int8Range(Int8(10), Int8(12))
|
||||
exp := NewFunc("UPPER", []Expression{mainRange}, nil)
|
||||
|
||||
got := rangeTypeCaster(mainRange, exp)
|
||||
_, ok := got.(IntegerExpression)
|
||||
if !ok {
|
||||
t.Errorf("expecting to get IntegerExpression but got %v", got)
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package jet
|
||||
|
||||
// GroupByClause interface
|
||||
type GroupByClause interface {
|
||||
serializeForGroupBy(statement StatementType, out *SQLBuilder)
|
||||
}
|
||||
|
||||
// GROUPING_SETS operator allows grouping of the rows in a table by multiple sets of columns in a single query.
|
||||
// This can be useful when we want to analyze data by different combinations of columns, without having to write separate
|
||||
// queries for each combination.
|
||||
func GROUPING_SETS(expressions ...Expression) GroupByClause {
|
||||
return Func("GROUPING SETS", expressions...)
|
||||
}
|
||||
|
||||
// ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list.
|
||||
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
|
||||
func ROLLUP(expressions ...Expression) GroupByClause {
|
||||
return Func("ROLLUP", expressions...)
|
||||
}
|
||||
|
||||
// CUBE operator is used with the GROUP BY clause to generate subtotals for all possible combinations of a group of columns.
|
||||
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
|
||||
func CUBE(expressions ...Expression) GroupByClause {
|
||||
return Func("CUBE", expressions...)
|
||||
}
|
||||
|
||||
// GROUPING function is used to identify which columns are included in a grouping set or a subtotal row. It takes as input
|
||||
// the name of a column and returns 1 if the column is not included in the current grouping set, and 0 otherwise.
|
||||
// It can be also used with multiple parameters to check if a set of columns is included in the current grouping set. The result
|
||||
// of the GROUPING function would then be an integer bit mask having 1’s for the arguments which have GROUPING(argument) as 1.
|
||||
func GROUPING(expressions ...Expression) IntegerExpression {
|
||||
return IntExp(Func("GROUPING", expressions...))
|
||||
}
|
||||
|
||||
// WITH_ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list.
|
||||
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
|
||||
func WITH_ROLLUP(expressions ...Expression) GroupByClause {
|
||||
return CustomExpression(
|
||||
parametersSerializer(expressions), Token("WITH ROLLUP"),
|
||||
)
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
package jet
|
||||
|
||||
// IntegerExpression interface
|
||||
type IntegerExpression interface {
|
||||
Expression
|
||||
numericExpression
|
||||
|
||||
EQ(rhs IntegerExpression) BoolExpression
|
||||
NOT_EQ(rhs IntegerExpression) BoolExpression
|
||||
IS_DISTINCT_FROM(rhs IntegerExpression) BoolExpression
|
||||
IS_NOT_DISTINCT_FROM(rhs IntegerExpression) BoolExpression
|
||||
|
||||
LT(rhs IntegerExpression) BoolExpression
|
||||
LT_EQ(rhs IntegerExpression) BoolExpression
|
||||
GT(rhs IntegerExpression) BoolExpression
|
||||
GT_EQ(rhs IntegerExpression) BoolExpression
|
||||
BETWEEN(min, max IntegerExpression) BoolExpression
|
||||
NOT_BETWEEN(min, max IntegerExpression) BoolExpression
|
||||
|
||||
ADD(rhs IntegerExpression) IntegerExpression
|
||||
SUB(rhs IntegerExpression) IntegerExpression
|
||||
MUL(rhs IntegerExpression) IntegerExpression
|
||||
DIV(rhs IntegerExpression) IntegerExpression
|
||||
MOD(rhs IntegerExpression) IntegerExpression
|
||||
POW(rhs IntegerExpression) IntegerExpression
|
||||
|
||||
BIT_AND(rhs IntegerExpression) IntegerExpression
|
||||
BIT_OR(rhs IntegerExpression) IntegerExpression
|
||||
BIT_XOR(rhs IntegerExpression) IntegerExpression
|
||||
BIT_SHIFT_LEFT(shift IntegerExpression) IntegerExpression
|
||||
BIT_SHIFT_RIGHT(shift IntegerExpression) IntegerExpression
|
||||
}
|
||||
|
||||
// additional integer expression subtypes, used in range expressions.
|
||||
type (
|
||||
Int4Expression IntegerExpression
|
||||
Int8Expression IntegerExpression
|
||||
)
|
||||
|
||||
type integerInterfaceImpl struct {
|
||||
numericExpressionImpl
|
||||
parent IntegerExpression
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) EQ(rhs IntegerExpression) BoolExpression {
|
||||
return Eq(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) NOT_EQ(rhs IntegerExpression) BoolExpression {
|
||||
return NotEq(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) IS_DISTINCT_FROM(rhs IntegerExpression) BoolExpression {
|
||||
return IsDistinctFrom(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs IntegerExpression) BoolExpression {
|
||||
return IsNotDistinctFrom(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) GT(rhs IntegerExpression) BoolExpression {
|
||||
return Gt(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) GT_EQ(rhs IntegerExpression) BoolExpression {
|
||||
return GtEq(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) LT(rhs IntegerExpression) BoolExpression {
|
||||
return Lt(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) LT_EQ(rhs IntegerExpression) BoolExpression {
|
||||
return LtEq(i.parent, rhs)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) BETWEEN(min, max IntegerExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(i.parent, min, max, false)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) NOT_BETWEEN(min, max IntegerExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(i.parent, min, max, true)
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) ADD(rhs IntegerExpression) IntegerExpression {
|
||||
return IntExp(Add(i.parent, rhs))
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) SUB(rhs IntegerExpression) IntegerExpression {
|
||||
return IntExp(Sub(i.parent, rhs))
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) MUL(rhs IntegerExpression) IntegerExpression {
|
||||
return IntExp(Mul(i.parent, rhs))
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) DIV(rhs IntegerExpression) IntegerExpression {
|
||||
return IntExp(Div(i.parent, rhs))
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) MOD(rhs IntegerExpression) IntegerExpression {
|
||||
return IntExp(Mod(i.parent, rhs))
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) POW(rhs IntegerExpression) IntegerExpression {
|
||||
return IntExp(POW(i.parent, rhs))
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) BIT_AND(rhs IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerOperatorExpression(i.parent, rhs, "&")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) BIT_OR(rhs IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerOperatorExpression(i.parent, rhs, "|")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) BIT_XOR(rhs IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerOperatorExpression(i.parent, rhs, "#")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) BIT_SHIFT_LEFT(intExpression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerOperatorExpression(i.parent, intExpression, "<<")
|
||||
}
|
||||
|
||||
func (i *integerInterfaceImpl) BIT_SHIFT_RIGHT(intExpression IntegerExpression) IntegerExpression {
|
||||
return newBinaryIntegerOperatorExpression(i.parent, intExpression, ">>")
|
||||
}
|
||||
|
||||
func newBinaryIntegerOperatorExpression(lhs, rhs IntegerExpression, operator string) IntegerExpression {
|
||||
return IntExp(NewBinaryOperatorExpression(lhs, rhs, operator))
|
||||
}
|
||||
|
||||
func newPrefixIntegerOperatorExpression(expression IntegerExpression, operator string) IntegerExpression {
|
||||
return IntExp(newPrefixOperatorExpression(expression, operator))
|
||||
}
|
||||
|
||||
type integerExpressionWrapper struct {
|
||||
integerInterfaceImpl
|
||||
|
||||
Expression
|
||||
}
|
||||
|
||||
func newIntExpressionWrap(expression Expression) IntegerExpression {
|
||||
intExpressionWrap := integerExpressionWrapper{Expression: expression}
|
||||
|
||||
intExpressionWrap.integerInterfaceImpl.parent = &intExpressionWrap
|
||||
|
||||
return &intExpressionWrap
|
||||
}
|
||||
|
||||
// IntExp is int expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as int expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func IntExp(expression Expression) IntegerExpression {
|
||||
return newIntExpressionWrap(expression)
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegerExpressionEQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.EQ(table2ColInt), "(table1.col_int = table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.EQ(Int(11)), "(table1.col_int = $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.NOT_EQ(table2ColInt), "(table1.col_int != table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.NOT_EQ(Int(11)), "(table1.col_int != $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionGT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.GT(table2ColInt), "(table1.col_int > table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.GT(Int(11)), "(table1.col_int > $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionGT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.GT_EQ(table2ColInt), "(table1.col_int >= table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.GT_EQ(Int(11)), "(table1.col_int >= $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionLT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.LT(table2ColInt), "(table1.col_int < table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.LT(Int(11)), "(table1.col_int < $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionLT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.LT_EQ(table2ColInt), "(table1.col_int <= table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.LT_EQ(Int(11)), "(table1.col_int <= $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionADD(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.ADD(table2ColInt), "(table1.col_int + table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.ADD(Int(11)), "(table1.col_int + $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionSUB(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.SUB(table2ColInt), "(table1.col_int - table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.SUB(Int(11)), "(table1.col_int - $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionMUL(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.MUL(table2ColInt), "(table1.col_int * table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.MUL(Int(11)), "(table1.col_int * $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntegerExpressionDIV(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.DIV(table2ColInt), "(table1.col_int / table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.DIV(Int(11)), "(table1.col_int / $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionMOD(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.MOD(table2ColInt), "(table1.col_int % table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.MOD(Int(11)), "(table1.col_int % $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionPOW(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.POW(table2ColInt), "POW(table1.col_int, table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.POW(Int(11)), "POW(table1.col_int, $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_NOT(t *testing.T) {
|
||||
assertClauseSerialize(t, BIT_NOT(table2ColInt), "(~ table2.col_int)")
|
||||
assertClauseSerialize(t, BIT_NOT(Int(11)), "(~ 11)")
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_AND(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.BIT_AND(table2ColInt), "(table1.col_int & table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.BIT_AND(Int(11)), "(table1.col_int & $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_OR(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.BIT_OR(table2ColInt), "(table1.col_int | table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.BIT_OR(Int(11)), "(table1.col_int | $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_XOR(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.BIT_XOR(table2ColInt), "(table1.col_int # table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.BIT_XOR(Int(11)), "(table1.col_int # $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_SHIFT_LEFT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_LEFT(table2ColInt), "(table1.col_int << table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_LEFT(Int(11)), "(table1.col_int << $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBIT_SHIFT_RIGHT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_RIGHT(table2ColInt), "(table1.col_int >> table2.col_int)")
|
||||
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_RIGHT(Int(11)), "(table1.col_int >> $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionIntExp(t *testing.T) {
|
||||
assertClauseSerialize(t, IntExp(table1ColFloat), "table1.col_float")
|
||||
assertClauseSerialize(t, IntExp(table1ColFloat.ADD(table2ColFloat)).ADD(Int(11)),
|
||||
"((table1.col_float + table2.col_float) + $1)", int64(11))
|
||||
}
|
||||
|
||||
func TestIntExpressionBetween(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColInt.BETWEEN(Int(1), table1Col3), "(table1.col_int BETWEEN $1 AND table1.col3)", int64(1))
|
||||
assertClauseSerialize(t, table1ColInt.BETWEEN(Int(1), table1Col3).AND(table1ColBool),
|
||||
"((table1.col_int BETWEEN $1 AND table1.col3) AND table1.col_bool)", int64(1))
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package jet
|
||||
|
||||
// Interval is internal common representation of sql interval
|
||||
type Interval interface {
|
||||
Serializer
|
||||
IsInterval
|
||||
}
|
||||
|
||||
// IsInterval interface
|
||||
type IsInterval interface {
|
||||
isInterval()
|
||||
}
|
||||
|
||||
// IsIntervalImpl is implementation of IsInterval interface
|
||||
type IsIntervalImpl struct{}
|
||||
|
||||
func (i *IsIntervalImpl) isInterval() {}
|
||||
|
||||
// NewInterval creates new interval from serializer
|
||||
func NewInterval(s Serializer) *IntervalImpl {
|
||||
newInterval := &IntervalImpl{
|
||||
Value: s,
|
||||
}
|
||||
|
||||
return newInterval
|
||||
}
|
||||
|
||||
// IntervalImpl is implementation of Interval type
|
||||
type IntervalImpl struct {
|
||||
Value Serializer
|
||||
IsIntervalImpl
|
||||
}
|
||||
|
||||
func (i IntervalImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.WriteString("INTERVAL")
|
||||
i.Value.serialize(statement, out, FallTrough(options)...)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package jet
|
||||
|
||||
const (
|
||||
// DEFAULT is jet equivalent of SQL DEFAULT
|
||||
DEFAULT Keyword = "DEFAULT"
|
||||
)
|
||||
|
||||
// Keyword type
|
||||
type Keyword string
|
||||
|
||||
func (k Keyword) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.WriteString(string(k))
|
||||
}
|
||||
@@ -1,480 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LiteralExpression is representation of an escaped literal
|
||||
type LiteralExpression interface {
|
||||
Expression
|
||||
|
||||
Value() interface{}
|
||||
SetConstant(constant bool)
|
||||
}
|
||||
|
||||
type literalExpressionImpl struct {
|
||||
ExpressionInterfaceImpl
|
||||
|
||||
value interface{}
|
||||
constant bool
|
||||
}
|
||||
|
||||
func literal(value interface{}, optionalConstant ...bool) *literalExpressionImpl {
|
||||
exp := literalExpressionImpl{value: value}
|
||||
|
||||
if len(optionalConstant) > 0 {
|
||||
exp.constant = optionalConstant[0]
|
||||
}
|
||||
|
||||
exp.ExpressionInterfaceImpl.Parent = &exp
|
||||
|
||||
return &exp
|
||||
}
|
||||
|
||||
// Literal is injected directly to SQL query, and does not appear in parametrized argument list.
|
||||
func Literal(value interface{}) *literalExpressionImpl {
|
||||
exp := literal(value)
|
||||
return exp
|
||||
}
|
||||
|
||||
// FixedLiteral is injected directly to SQL query, and does not appear in parametrized argument list.
|
||||
func FixedLiteral(value interface{}) *literalExpressionImpl {
|
||||
exp := literal(value)
|
||||
exp.constant = true
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
func (l *literalExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if l.constant {
|
||||
out.insertConstantArgument(l.value)
|
||||
} else {
|
||||
out.insertParametrizedArgument(l.value)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *literalExpressionImpl) Value() interface{} {
|
||||
return l.value
|
||||
}
|
||||
|
||||
func (l *literalExpressionImpl) SetConstant(constant bool) {
|
||||
l.constant = constant
|
||||
}
|
||||
|
||||
type integerLiteralExpression struct {
|
||||
literalExpressionImpl
|
||||
integerInterfaceImpl
|
||||
}
|
||||
|
||||
func intLiteral(value interface{}) IntegerExpression {
|
||||
numLiteral := &integerLiteralExpression{}
|
||||
|
||||
numLiteral.literalExpressionImpl = *literal(value)
|
||||
|
||||
numLiteral.literalExpressionImpl.Parent = numLiteral
|
||||
numLiteral.integerInterfaceImpl.parent = numLiteral
|
||||
|
||||
return numLiteral
|
||||
}
|
||||
|
||||
// Int creates a new 64 bit signed integer literal
|
||||
func Int(value int64) IntegerExpression {
|
||||
return intLiteral(value)
|
||||
}
|
||||
|
||||
// Int8 creates a new 8 bit signed integer literal
|
||||
func Int8(value int8) IntegerExpression {
|
||||
return intLiteral(value)
|
||||
}
|
||||
|
||||
// Int16 creates a new 16 bit signed integer literal
|
||||
func Int16(value int16) IntegerExpression {
|
||||
return intLiteral(value)
|
||||
}
|
||||
|
||||
// Int32 creates a new 32 bit signed integer literal
|
||||
func Int32(value int32) IntegerExpression {
|
||||
return intLiteral(value)
|
||||
}
|
||||
|
||||
// Uint8 creates a new 8 bit unsigned integer literal
|
||||
func Uint8(value uint8) IntegerExpression {
|
||||
return intLiteral(value)
|
||||
}
|
||||
|
||||
// Uint16 creates a new 16 bit unsigned integer literal
|
||||
func Uint16(value uint16) IntegerExpression {
|
||||
return intLiteral(value)
|
||||
}
|
||||
|
||||
// Uint32 creates a new 32 bit unsigned integer literal
|
||||
func Uint32(value uint32) IntegerExpression {
|
||||
return intLiteral(value)
|
||||
}
|
||||
|
||||
// Uint64 creates a new 64 bit unsigned integer literal
|
||||
func Uint64(value uint64) IntegerExpression {
|
||||
return intLiteral(value)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------//
|
||||
type boolLiteralExpression struct {
|
||||
boolInterfaceImpl
|
||||
literalExpressionImpl
|
||||
}
|
||||
|
||||
// Bool creates new bool literal expression
|
||||
func Bool(value bool) BoolExpression {
|
||||
boolLiteralExpression := boolLiteralExpression{}
|
||||
|
||||
boolLiteralExpression.literalExpressionImpl = *literal(value)
|
||||
boolLiteralExpression.boolInterfaceImpl.parent = &boolLiteralExpression
|
||||
|
||||
return &boolLiteralExpression
|
||||
}
|
||||
|
||||
// ---------------------------------------------------//
|
||||
type floatLiteral struct {
|
||||
floatInterfaceImpl
|
||||
literalExpressionImpl
|
||||
}
|
||||
|
||||
// Float creates new float literal from float64 value
|
||||
func Float(value float64) FloatExpression {
|
||||
floatLiteral := floatLiteral{}
|
||||
floatLiteral.literalExpressionImpl = *literal(value)
|
||||
|
||||
floatLiteral.floatInterfaceImpl.parent = &floatLiteral
|
||||
|
||||
return &floatLiteral
|
||||
}
|
||||
|
||||
// Decimal creates new float literal from string value
|
||||
func Decimal(value string) FloatExpression {
|
||||
floatLiteral := floatLiteral{}
|
||||
floatLiteral.literalExpressionImpl = *literal(value)
|
||||
|
||||
floatLiteral.floatInterfaceImpl.parent = &floatLiteral
|
||||
|
||||
return &floatLiteral
|
||||
}
|
||||
|
||||
// ---------------------------------------------------//
|
||||
type stringLiteral struct {
|
||||
stringInterfaceImpl
|
||||
literalExpressionImpl
|
||||
}
|
||||
|
||||
// String creates new string literal expression
|
||||
func String(value string) StringExpression {
|
||||
stringLiteral := stringLiteral{}
|
||||
stringLiteral.literalExpressionImpl = *literal(value)
|
||||
|
||||
stringLiteral.stringInterfaceImpl.parent = &stringLiteral
|
||||
|
||||
return &stringLiteral
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type timeLiteral struct {
|
||||
timeInterfaceImpl
|
||||
literalExpressionImpl
|
||||
}
|
||||
|
||||
// Time creates new time literal expression
|
||||
func Time(hour, minute, second int, nanoseconds ...time.Duration) TimeExpression {
|
||||
timeLiteral := &timeLiteral{}
|
||||
timeStr := fmt.Sprintf("%02d:%02d:%02d", hour, minute, second)
|
||||
timeStr += formatNanoseconds(nanoseconds...)
|
||||
timeLiteral.literalExpressionImpl = *literal(timeStr)
|
||||
|
||||
timeLiteral.timeInterfaceImpl.parent = timeLiteral
|
||||
|
||||
return timeLiteral
|
||||
}
|
||||
|
||||
// TimeT creates new time literal expression from time.Time object
|
||||
func TimeT(t time.Time) TimeExpression {
|
||||
timeLiteral := &timeLiteral{}
|
||||
timeLiteral.literalExpressionImpl = *literal(t)
|
||||
timeLiteral.timeInterfaceImpl.parent = timeLiteral
|
||||
|
||||
return timeLiteral
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type timezLiteral struct {
|
||||
timezInterfaceImpl
|
||||
literalExpressionImpl
|
||||
}
|
||||
|
||||
// Timez creates new time with time zone literal expression
|
||||
func Timez(hour, minute, second int, nanoseconds time.Duration, timezone string) TimezExpression {
|
||||
timezLiteral := timezLiteral{}
|
||||
timeStr := fmt.Sprintf("%02d:%02d:%02d", hour, minute, second)
|
||||
timeStr += formatNanoseconds(nanoseconds)
|
||||
timeStr += " " + timezone
|
||||
timezLiteral.literalExpressionImpl = *literal(timeStr)
|
||||
|
||||
return TimezExp(literal(timeStr))
|
||||
}
|
||||
|
||||
// TimezT creates new time with time zone literal expression from time.Time object
|
||||
func TimezT(t time.Time) TimezExpression {
|
||||
timeLiteral := &timezLiteral{}
|
||||
timeLiteral.literalExpressionImpl = *literal(t)
|
||||
timeLiteral.timezInterfaceImpl.parent = timeLiteral
|
||||
|
||||
return timeLiteral
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type timestampLiteral struct {
|
||||
timestampInterfaceImpl
|
||||
literalExpressionImpl
|
||||
}
|
||||
|
||||
// Timestamp creates new timestamp literal expression
|
||||
func Timestamp(year int, month time.Month, day, hour, minute, second int, nanoseconds ...time.Duration) TimestampExpression {
|
||||
timestamp := ×tampLiteral{}
|
||||
timeStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
|
||||
timeStr += formatNanoseconds(nanoseconds...)
|
||||
timestamp.literalExpressionImpl = *literal(timeStr)
|
||||
timestamp.timestampInterfaceImpl.parent = timestamp
|
||||
return timestamp
|
||||
}
|
||||
|
||||
// TimestampT creates new timestamp literal expression from time.Time object
|
||||
func TimestampT(t time.Time) TimestampExpression {
|
||||
timestamp := ×tampLiteral{}
|
||||
timestamp.literalExpressionImpl = *literal(t)
|
||||
timestamp.timestampInterfaceImpl.parent = timestamp
|
||||
return timestamp
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type timestampzLiteral struct {
|
||||
timestampzInterfaceImpl
|
||||
literalExpressionImpl
|
||||
}
|
||||
|
||||
// Timestampz creates new timestamp with time zone literal expression
|
||||
func Timestampz(year int, month time.Month, day, hour, minute, second int, nanoseconds time.Duration, timezone string) TimestampzExpression {
|
||||
timestamp := ×tampzLiteral{}
|
||||
timeStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
|
||||
timeStr += formatNanoseconds(nanoseconds)
|
||||
timeStr += " " + timezone
|
||||
|
||||
timestamp.literalExpressionImpl = *literal(timeStr)
|
||||
timestamp.timestampzInterfaceImpl.parent = timestamp
|
||||
return timestamp
|
||||
}
|
||||
|
||||
// TimestampzT creates new timestamp literal expression from time.Time object
|
||||
func TimestampzT(t time.Time) TimestampzExpression {
|
||||
timestamp := ×tampzLiteral{}
|
||||
timestamp.literalExpressionImpl = *literal(t)
|
||||
timestamp.timestampzInterfaceImpl.parent = timestamp
|
||||
return timestamp
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type dateLiteral struct {
|
||||
dateInterfaceImpl
|
||||
literalExpressionImpl
|
||||
}
|
||||
|
||||
// Date creates new date literal expression
|
||||
func Date(year int, month time.Month, day int) DateExpression {
|
||||
dateLiteral := &dateLiteral{}
|
||||
|
||||
timeStr := fmt.Sprintf("%04d-%02d-%02d", year, month, day)
|
||||
dateLiteral.literalExpressionImpl = *literal(timeStr)
|
||||
dateLiteral.dateInterfaceImpl.parent = dateLiteral
|
||||
|
||||
return dateLiteral
|
||||
}
|
||||
|
||||
// DateT creates new date literal expression from time.Time object
|
||||
func DateT(t time.Time) DateExpression {
|
||||
dateLiteral := &dateLiteral{}
|
||||
dateLiteral.literalExpressionImpl = *literal(t)
|
||||
dateLiteral.dateInterfaceImpl.parent = dateLiteral
|
||||
|
||||
return dateLiteral
|
||||
}
|
||||
|
||||
func formatNanoseconds(nanoseconds ...time.Duration) string {
|
||||
if len(nanoseconds) > 0 && nanoseconds[0] != 0 {
|
||||
duration := fmt.Sprintf("%09d", nanoseconds[0])
|
||||
i := len(duration) - 1
|
||||
for ; i >= 3; i-- {
|
||||
if duration[i] != '0' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return "." + duration[0:i+1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
//--------------------------------------------------//
|
||||
|
||||
var (
|
||||
// NULL is jet equivalent of SQL NULL
|
||||
NULL = newNullLiteral()
|
||||
// STAR is jet equivalent of SQL *
|
||||
STAR = newStarLiteral()
|
||||
// PLUS_INFINITY is jet equivalent for sql infinity
|
||||
PLUS_INFINITY = String("infinity")
|
||||
// MINUS_INFINITY is jet equivalent for sql -infinity
|
||||
MINUS_INFINITY = String("-infinity")
|
||||
)
|
||||
|
||||
type nullLiteral struct {
|
||||
ExpressionInterfaceImpl
|
||||
}
|
||||
|
||||
func newNullLiteral() Expression {
|
||||
nullExpression := &nullLiteral{}
|
||||
|
||||
nullExpression.ExpressionInterfaceImpl.Parent = nullExpression
|
||||
|
||||
return nullExpression
|
||||
}
|
||||
|
||||
func (n *nullLiteral) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.WriteString("NULL")
|
||||
}
|
||||
|
||||
// --------------------------------------------------//
|
||||
type starLiteral struct {
|
||||
ExpressionInterfaceImpl
|
||||
}
|
||||
|
||||
func newStarLiteral() Expression {
|
||||
starExpression := &starLiteral{}
|
||||
|
||||
starExpression.ExpressionInterfaceImpl.Parent = starExpression
|
||||
|
||||
return starExpression
|
||||
}
|
||||
|
||||
func (n *starLiteral) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.WriteString("*")
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type rawExpression struct {
|
||||
ExpressionInterfaceImpl
|
||||
|
||||
Raw string
|
||||
NamedArgument map[string]interface{}
|
||||
noWrap bool
|
||||
}
|
||||
|
||||
func (n *rawExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if !n.noWrap && !contains(options, NoWrap) {
|
||||
out.WriteByte('(')
|
||||
}
|
||||
|
||||
out.insertRawQuery(n.Raw, n.NamedArgument)
|
||||
|
||||
if !n.noWrap && !contains(options, NoWrap) {
|
||||
out.WriteByte(')')
|
||||
}
|
||||
}
|
||||
|
||||
// Raw can be used for any unsupported functions, operators or expressions.
|
||||
// For example: Raw("current_database()")
|
||||
func Raw(raw string, namedArgs ...map[string]interface{}) Expression {
|
||||
var namedArguments map[string]interface{}
|
||||
|
||||
if len(namedArgs) > 0 {
|
||||
namedArguments = namedArgs[0]
|
||||
}
|
||||
|
||||
rawExp := &rawExpression{
|
||||
Raw: raw,
|
||||
NamedArgument: namedArguments,
|
||||
}
|
||||
rawExp.ExpressionInterfaceImpl.Parent = rawExp
|
||||
|
||||
return rawExp
|
||||
}
|
||||
|
||||
// RawWithParent is a Raw constructor used for construction dialect specific expression
|
||||
func RawWithParent(raw string, parent ...Expression) Expression {
|
||||
rawExp := &rawExpression{
|
||||
Raw: raw,
|
||||
noWrap: true,
|
||||
}
|
||||
rawExp.ExpressionInterfaceImpl.Parent = OptionalOrDefaultExpression(rawExp, parent...)
|
||||
|
||||
return rawExp
|
||||
}
|
||||
|
||||
// RawBool helper that for raw string boolean expressions
|
||||
func RawBool(raw string, namedArgs ...map[string]interface{}) BoolExpression {
|
||||
return BoolExp(Raw(raw, namedArgs...))
|
||||
}
|
||||
|
||||
// RawInt helper that for integer expressions
|
||||
func RawInt(raw string, namedArgs ...map[string]interface{}) IntegerExpression {
|
||||
return IntExp(Raw(raw, namedArgs...))
|
||||
}
|
||||
|
||||
// RawFloat helper that for float expressions
|
||||
func RawFloat(raw string, namedArgs ...map[string]interface{}) FloatExpression {
|
||||
return FloatExp(Raw(raw, namedArgs...))
|
||||
}
|
||||
|
||||
// RawString helper that for string expressions
|
||||
func RawString(raw string, namedArgs ...map[string]interface{}) StringExpression {
|
||||
return StringExp(Raw(raw, namedArgs...))
|
||||
}
|
||||
|
||||
// RawTime helper that for time expressions
|
||||
func RawTime(raw string, namedArgs ...map[string]interface{}) TimeExpression {
|
||||
return TimeExp(Raw(raw, namedArgs...))
|
||||
}
|
||||
|
||||
// RawTimez helper that for time with time zone expressions
|
||||
func RawTimez(raw string, namedArgs ...map[string]interface{}) TimezExpression {
|
||||
return TimezExp(Raw(raw, namedArgs...))
|
||||
}
|
||||
|
||||
// RawTimestamp helper that for timestamp expressions
|
||||
func RawTimestamp(raw string, namedArgs ...map[string]interface{}) TimestampExpression {
|
||||
return TimestampExp(Raw(raw, namedArgs...))
|
||||
}
|
||||
|
||||
// RawTimestampz helper that for timestamp with time zone expressions
|
||||
func RawTimestampz(raw string, namedArgs ...map[string]interface{}) TimestampzExpression {
|
||||
return TimestampzExp(Raw(raw, namedArgs...))
|
||||
}
|
||||
|
||||
// RawDate helper that for date expressions
|
||||
func RawDate(raw string, namedArgs ...map[string]interface{}) DateExpression {
|
||||
return DateExp(Raw(raw, namedArgs...))
|
||||
}
|
||||
|
||||
// RawRange helper that for range expressions
|
||||
func RawRange[T Expression](raw string, namedArgs ...map[string]interface{}) Range[T] {
|
||||
return RangeExp[T](Raw(raw, namedArgs...))
|
||||
}
|
||||
|
||||
// UUID is a helper function to create string literal expression from uuid object
|
||||
// value can be any uuid type with a String method
|
||||
func UUID(value fmt.Stringer) StringExpression {
|
||||
return String(value.String())
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRawExpression(t *testing.T) {
|
||||
assertClauseSerialize(t, Raw("current_database()"), "(current_database())")
|
||||
|
||||
var timeT = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
|
||||
|
||||
assertClauseSerialize(t, DateT(timeT), "$1", timeT)
|
||||
}
|
||||
|
||||
func TestTimeLiteral(t *testing.T) {
|
||||
assertClauseDebugSerialize(t, Time(11, 5, 30), "'11:05:30'")
|
||||
assertClauseDebugSerialize(t, Time(11, 5, 30, 0), "'11:05:30'")
|
||||
assertClauseDebugSerialize(t, Time(11, 5, 30, 3*time.Millisecond), "'11:05:30.003'")
|
||||
assertClauseDebugSerialize(t, Time(11, 5, 30, 30*time.Millisecond), "'11:05:30.030'")
|
||||
assertClauseDebugSerialize(t, Time(11, 5, 30, 300*time.Millisecond), "'11:05:30.300'")
|
||||
assertClauseDebugSerialize(t, Time(11, 5, 30, 300*time.Microsecond), "'11:05:30.0003'")
|
||||
assertClauseDebugSerialize(t, Time(11, 5, 30, 4*time.Nanosecond), "'11:05:30.000000004'")
|
||||
}
|
||||
|
||||
func TestTimeT(t *testing.T) {
|
||||
timeT := time.Date(2000, 1, 1, 11, 40, 20, 124, time.UTC)
|
||||
assertClauseDebugSerialize(t, TimeT(timeT), `'2000-01-01 11:40:20.000000124Z'`)
|
||||
}
|
||||
|
||||
func TestTimezLiteral(t *testing.T) {
|
||||
assertClauseDebugSerialize(t, Timez(11, 5, 30, 10*time.Nanosecond, "UTC"), "'11:05:30.00000001 UTC'")
|
||||
assertClauseDebugSerialize(t, Timez(11, 5, 30, 0, "+1"), "'11:05:30 +1'")
|
||||
assertClauseDebugSerialize(t, Timez(11, 5, 30, 3*time.Microsecond, "-7"), "'11:05:30.000003 -7'")
|
||||
assertClauseDebugSerialize(t, Timez(11, 5, 30, 30*time.Millisecond, "+8:00"), "'11:05:30.030 +8:00'")
|
||||
assertClauseDebugSerialize(t, Timez(11, 5, 30, 300*time.Nanosecond, "America/New_Yor"), "'11:05:30.0000003 America/New_Yor'")
|
||||
assertClauseDebugSerialize(t, Timez(11, 5, 30, 3000*time.Nanosecond, "zulu"), "'11:05:30.000003 zulu'")
|
||||
}
|
||||
|
||||
func TestTimestampLiteral(t *testing.T) {
|
||||
assertClauseDebugSerialize(t, Timestamp(2011, 1, 8, 11, 5, 30), "'2011-01-08 11:05:30'")
|
||||
assertClauseDebugSerialize(t, Timestamp(2011, 2, 7, 11, 5, 30, 0), "'2011-02-07 11:05:30'")
|
||||
assertClauseDebugSerialize(t, Timestamp(2011, 3, 6, 11, 5, 30, 3*time.Millisecond), "'2011-03-06 11:05:30.003'")
|
||||
assertClauseDebugSerialize(t, Timestamp(2011, 4, 5, 11, 5, 30, 30*time.Millisecond), "'2011-04-05 11:05:30.030'")
|
||||
assertClauseDebugSerialize(t, Timestamp(2011, 5, 4, 11, 5, 30, 300*time.Millisecond), "'2011-05-04 11:05:30.300'")
|
||||
assertClauseDebugSerialize(t, Timestamp(2011, 6, 3, 11, 5, 30, 3000*time.Microsecond), "'2011-06-03 11:05:30.003'")
|
||||
}
|
||||
|
||||
func TestTimestampzLiteral(t *testing.T) {
|
||||
assertClauseDebugSerialize(t, Timestampz(2011, 1, 8, 11, 5, 30, 0, "UTC"), "'2011-01-08 11:05:30 UTC'")
|
||||
assertClauseDebugSerialize(t, Timestampz(2011, 2, 7, 11, 5, 30, 0, "PST"), "'2011-02-07 11:05:30 PST'")
|
||||
assertClauseDebugSerialize(t, Timestampz(2011, 3, 6, 11, 5, 30, 3, "+4:00"), "'2011-03-06 11:05:30.000000003 +4:00'")
|
||||
assertClauseDebugSerialize(t, Timestampz(2011, 4, 5, 11, 5, 30, 30, "-8:00"), "'2011-04-05 11:05:30.00000003 -8:00'")
|
||||
assertClauseDebugSerialize(t, Timestampz(2011, 5, 4, 11, 5, 30, 300, "400"), "'2011-05-04 11:05:30.0000003 400'")
|
||||
assertClauseDebugSerialize(t, Timestampz(2011, 6, 3, 11, 5, 30, 3000, "zulu"), "'2011-06-03 11:05:30.000003 zulu'")
|
||||
}
|
||||
|
||||
func TestDate(t *testing.T) {
|
||||
assertClauseDebugSerialize(t, Date(2019, 8, 8), `'2019-08-08'`)
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PrintableStatement is a statement which sql query can be logged
|
||||
type PrintableStatement interface {
|
||||
Sql() (query string, args []interface{})
|
||||
DebugSql() (query string)
|
||||
}
|
||||
|
||||
// LoggerFunc is a function user can implement to support automatic statement logging.
|
||||
type LoggerFunc func(ctx context.Context, statement PrintableStatement)
|
||||
|
||||
var logger LoggerFunc
|
||||
|
||||
// SetLoggerFunc sets automatic statement logging
|
||||
func SetLoggerFunc(loggerFunc LoggerFunc) {
|
||||
logger = loggerFunc
|
||||
}
|
||||
|
||||
func callLogger(ctx context.Context, statement Statement) {
|
||||
if logger != nil {
|
||||
logger(ctx, statement)
|
||||
}
|
||||
}
|
||||
|
||||
// QueryInfo contains information about executed query
|
||||
type QueryInfo struct {
|
||||
Statement PrintableStatement
|
||||
// Depending on how the statement is executed, RowsProcessed is:
|
||||
// - Number of rows returned for Query() and QueryContext() methods
|
||||
// - RowsAffected() for Exec() and ExecContext() methods
|
||||
// - Always 0 for Rows() method.
|
||||
RowsProcessed int64
|
||||
Duration time.Duration
|
||||
Err error
|
||||
}
|
||||
|
||||
// QueryLoggerFunc is a function user can implement to retrieve more information about statement executed.
|
||||
type QueryLoggerFunc func(ctx context.Context, info QueryInfo)
|
||||
|
||||
var queryLoggerFunc QueryLoggerFunc
|
||||
|
||||
// SetQueryLogger sets automatic query logging function.
|
||||
func SetQueryLogger(loggerFunc QueryLoggerFunc) {
|
||||
queryLoggerFunc = loggerFunc
|
||||
}
|
||||
|
||||
func callQueryLoggerFunc(ctx context.Context, info QueryInfo) {
|
||||
if queryLoggerFunc != nil {
|
||||
queryLoggerFunc(ctx, info)
|
||||
}
|
||||
}
|
||||
|
||||
// Caller returns information about statement caller
|
||||
func (q QueryInfo) Caller() (file string, line int, function string) {
|
||||
skip := 4
|
||||
// depending on execution type (Query, QueryContext, Exec, ...) looped once or twice
|
||||
for {
|
||||
var pc uintptr
|
||||
var ok bool
|
||||
|
||||
pc, file, line, ok = runtime.Caller(skip)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
funcDetails := runtime.FuncForPC(pc)
|
||||
if !strings.Contains(funcDetails.Name(), "github.com/go-jet/jet/v2/internal") {
|
||||
function = funcDetails.Name()
|
||||
return
|
||||
}
|
||||
|
||||
skip++
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package jet
|
||||
|
||||
// NumericExpression is common interface for all integer and float expressions
|
||||
type NumericExpression interface {
|
||||
Expression
|
||||
numericExpression
|
||||
}
|
||||
|
||||
type numericExpression interface {
|
||||
isNumericExpression()
|
||||
}
|
||||
|
||||
type numericExpressionImpl struct{}
|
||||
|
||||
func (n *numericExpressionImpl) isNumericExpression() {}
|
||||
@@ -1,194 +0,0 @@
|
||||
package jet
|
||||
|
||||
// Operators
|
||||
const (
|
||||
StringConcatOperator = "||"
|
||||
StringRegexpLikeOperator = "REGEXP"
|
||||
StringNotRegexpLikeOperator = "NOT REGEXP"
|
||||
)
|
||||
|
||||
//----------- Logical operators ---------------//
|
||||
|
||||
// NOT returns negation of bool expression result
|
||||
func NOT(exp BoolExpression) BoolExpression {
|
||||
return newPrefixBoolOperatorExpression(exp, "NOT")
|
||||
}
|
||||
|
||||
// BIT_NOT inverts every bit in integer expression result
|
||||
func BIT_NOT(expr IntegerExpression) IntegerExpression {
|
||||
if literalExp, ok := expr.(LiteralExpression); ok {
|
||||
literalExp.SetConstant(true)
|
||||
}
|
||||
return newPrefixIntegerOperatorExpression(expr, "~")
|
||||
}
|
||||
|
||||
//----------- Comparison operators ---------------//
|
||||
|
||||
// EXISTS checks for existence of the rows in subQuery
|
||||
func EXISTS(subQuery Expression) BoolExpression {
|
||||
return newPrefixBoolOperatorExpression(subQuery, "EXISTS")
|
||||
}
|
||||
|
||||
// Eq returns a representation of "a=b"
|
||||
func Eq(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, "=")
|
||||
}
|
||||
|
||||
// NotEq returns a representation of "a!=b"
|
||||
func NotEq(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, "!=")
|
||||
}
|
||||
|
||||
// IsDistinctFrom returns a representation of "a IS DISTINCT FROM b"
|
||||
func IsDistinctFrom(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, "IS DISTINCT FROM")
|
||||
}
|
||||
|
||||
// IsNotDistinctFrom returns a representation of "a IS NOT DISTINCT FROM b"
|
||||
func IsNotDistinctFrom(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, "IS NOT DISTINCT FROM")
|
||||
}
|
||||
|
||||
// Lt returns a representation of "a<b"
|
||||
func Lt(lhs Expression, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, "<")
|
||||
}
|
||||
|
||||
// LtEq returns a representation of "a<=b"
|
||||
func LtEq(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, "<=")
|
||||
}
|
||||
|
||||
// Gt returns a representation of "a>b"
|
||||
func Gt(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, ">")
|
||||
}
|
||||
|
||||
// GtEq returns a representation of "a>=b"
|
||||
func GtEq(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, ">=")
|
||||
}
|
||||
|
||||
// Contains returns a representation of "a @> b"
|
||||
func Contains(lhs Expression, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, "@>")
|
||||
}
|
||||
|
||||
// Overlap returns a representation of "a && b"
|
||||
func Overlap(lhs, rhs Expression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(lhs, rhs, "&&")
|
||||
}
|
||||
|
||||
// Add notEq returns a representation of "a + b"
|
||||
func Add(lhs, rhs Serializer) Expression {
|
||||
return NewBinaryOperatorExpression(lhs, rhs, "+")
|
||||
}
|
||||
|
||||
// Sub notEq returns a representation of "a - b"
|
||||
func Sub(lhs, rhs Serializer) Expression {
|
||||
return NewBinaryOperatorExpression(lhs, rhs, "-")
|
||||
}
|
||||
|
||||
// Mul returns a representation of "a * b"
|
||||
func Mul(lhs, rhs Serializer) Expression {
|
||||
return NewBinaryOperatorExpression(lhs, rhs, "*")
|
||||
}
|
||||
|
||||
// Div returns a representation of "a / b"
|
||||
func Div(lhs, rhs Serializer) Expression {
|
||||
return NewBinaryOperatorExpression(lhs, rhs, "/")
|
||||
}
|
||||
|
||||
// Mod returns a representation of "a % b"
|
||||
func Mod(lhs, rhs Serializer) Expression {
|
||||
return NewBinaryOperatorExpression(lhs, rhs, "%")
|
||||
}
|
||||
|
||||
// --------------- CASE operator -------------------//
|
||||
|
||||
// CaseOperator is interface for SQL case operator
|
||||
type CaseOperator interface {
|
||||
Expression
|
||||
|
||||
WHEN(condition Expression) CaseOperator
|
||||
THEN(then Expression) CaseOperator
|
||||
ELSE(els Expression) CaseOperator
|
||||
}
|
||||
|
||||
type caseOperatorImpl struct {
|
||||
ExpressionInterfaceImpl
|
||||
|
||||
expression Expression
|
||||
when []Expression
|
||||
then []Expression
|
||||
els Expression
|
||||
}
|
||||
|
||||
// CASE create CASE operator with optional list of expressions
|
||||
func CASE(expression ...Expression) CaseOperator {
|
||||
caseExp := &caseOperatorImpl{}
|
||||
|
||||
if len(expression) > 0 {
|
||||
caseExp.expression = expression[0]
|
||||
}
|
||||
|
||||
caseExp.ExpressionInterfaceImpl.Parent = caseExp
|
||||
|
||||
return caseExp
|
||||
}
|
||||
|
||||
func (c *caseOperatorImpl) WHEN(when Expression) CaseOperator {
|
||||
c.when = append(c.when, when)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *caseOperatorImpl) THEN(then Expression) CaseOperator {
|
||||
c.then = append(c.then, then)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *caseOperatorImpl) ELSE(els Expression) CaseOperator {
|
||||
c.els = els
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *caseOperatorImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.WriteString("(CASE")
|
||||
|
||||
if c.expression != nil {
|
||||
c.expression.serialize(statement, out, FallTrough(options)...)
|
||||
}
|
||||
|
||||
if len(c.when) == 0 || len(c.then) == 0 {
|
||||
panic("jet: invalid case Statement. There should be at least one WHEN/THEN pair. ")
|
||||
}
|
||||
|
||||
if len(c.when) != len(c.then) {
|
||||
panic("jet: WHEN and THEN expression count mismatch. ")
|
||||
}
|
||||
|
||||
for i, when := range c.when {
|
||||
out.WriteString("WHEN")
|
||||
when.serialize(statement, out, NoWrap)
|
||||
|
||||
out.WriteString("THEN")
|
||||
c.then[i].serialize(statement, out, NoWrap)
|
||||
}
|
||||
|
||||
if c.els != nil {
|
||||
out.WriteString("ELSE")
|
||||
c.els.serialize(statement, out, NoWrap)
|
||||
}
|
||||
|
||||
out.WriteString("END)")
|
||||
}
|
||||
|
||||
// DISTINCT operator can be used to return distinct values of expr
|
||||
func DISTINCT(expr Expression) Expression {
|
||||
return newPrefixOperatorExpression(expr, "DISTINCT")
|
||||
}
|
||||
|
||||
func BinaryOperator(lhs Expression, rhs Expression, operator string) Expression {
|
||||
return NewBinaryOperatorExpression(lhs, rhs, operator)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package jet
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOperatorNOT(t *testing.T) {
|
||||
notExpression := NOT(Int(2).EQ(Int(1)))
|
||||
|
||||
assertClauseSerialize(t, NOT(table1ColBool), "(NOT table1.col_bool)")
|
||||
assertClauseSerialize(t, notExpression, "(NOT ($1 = $2))", int64(2), int64(1))
|
||||
assertProjectionSerialize(t, notExpression.AS("alias_not_expression"), `(NOT ($1 = $2)) AS "alias_not_expression"`, int64(2), int64(1))
|
||||
assertClauseSerialize(t, notExpression.AND(Int(4).EQ(Int(5))), `((NOT ($1 = $2)) AND ($3 = $4))`, int64(2), int64(1), int64(4), int64(5))
|
||||
}
|
||||
|
||||
func TestCase1(t *testing.T) {
|
||||
query := CASE().
|
||||
WHEN(table3Col1.EQ(Int(1))).THEN(table3Col1.ADD(Int(1))).
|
||||
WHEN(table3Col1.EQ(Int(2))).THEN(table3Col1.ADD(Int(2)))
|
||||
|
||||
assertClauseSerialize(t, query, `(CASE WHEN table3.col1 = $1 THEN table3.col1 + $2 WHEN table3.col1 = $3 THEN table3.col1 + $4 END)`,
|
||||
int64(1), int64(1), int64(2), int64(2))
|
||||
}
|
||||
|
||||
func TestCase2(t *testing.T) {
|
||||
query := CASE(table3Col1).
|
||||
WHEN(Int(1)).THEN(table3Col1.ADD(Int(1))).
|
||||
WHEN(Int(2)).THEN(table3Col1.ADD(Int(2))).
|
||||
ELSE(Int(0))
|
||||
|
||||
assertClauseSerialize(t, query, `(CASE table3.col1 WHEN $1 THEN table3.col1 + $2 WHEN $3 THEN table3.col1 + $4 ELSE $5 END)`,
|
||||
int64(1), int64(1), int64(2), int64(2), int64(0))
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package jet
|
||||
|
||||
// OrderByClause interface
|
||||
type OrderByClause interface {
|
||||
// NULLS_FIRST specifies sort where null values appear before all non-null values.
|
||||
// For some dialects(mysql,mariadb), which do not support NULL_FIRST, NULL_FIRST is simulated
|
||||
// with additional IS_NOT_NULL expression.
|
||||
// For instance,
|
||||
// Rental.ReturnDate.DESC().NULLS_FIRST()
|
||||
// would translate to,
|
||||
// rental.return_date IS NOT NULL, rental.return_date DESC
|
||||
NULLS_FIRST() OrderByClause
|
||||
|
||||
// NULLS_LAST specifies sort where null values appear after all non-null values.
|
||||
// For some dialects(mysql,mariadb), which do not support NULLS_LAST, NULLS_LAST is simulated
|
||||
// with additional IS_NULL expression.
|
||||
// For instance,
|
||||
// Rental.ReturnDate.ASC().NULLS_LAST()
|
||||
// would translate to,
|
||||
// rental.return_date IS NULL, rental.return_date ASC
|
||||
NULLS_LAST() OrderByClause
|
||||
|
||||
serializeForOrderBy(statement StatementType, out *SQLBuilder)
|
||||
}
|
||||
|
||||
type orderByClauseImpl struct {
|
||||
expression Expression
|
||||
ascending *bool
|
||||
nullsFirst *bool
|
||||
}
|
||||
|
||||
func (ord *orderByClauseImpl) NULLS_FIRST() OrderByClause {
|
||||
nullsFirst := true
|
||||
ord.nullsFirst = &nullsFirst
|
||||
return ord
|
||||
}
|
||||
func (ord *orderByClauseImpl) NULLS_LAST() OrderByClause {
|
||||
nullsFirst := false
|
||||
ord.nullsFirst = &nullsFirst
|
||||
return ord
|
||||
}
|
||||
|
||||
func (ord *orderByClauseImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
|
||||
customSerializer := out.Dialect.SerializeOrderBy()
|
||||
if customSerializer != nil {
|
||||
customSerializer(ord.expression, ord.ascending, ord.nullsFirst)(statement, out)
|
||||
return
|
||||
}
|
||||
|
||||
if ord.expression == nil {
|
||||
panic("jet: nil expression in ORDER BY clause")
|
||||
}
|
||||
|
||||
ord.expression.serializeForOrderBy(statement, out)
|
||||
|
||||
if ord.ascending != nil {
|
||||
if *ord.ascending {
|
||||
out.WriteString("ASC")
|
||||
} else {
|
||||
out.WriteString("DESC")
|
||||
}
|
||||
}
|
||||
|
||||
if ord.nullsFirst != nil {
|
||||
if *ord.nullsFirst {
|
||||
out.WriteString("NULLS FIRST")
|
||||
} else {
|
||||
out.WriteString("NULLS LAST")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newOrderByAscending(expression Expression, ascending bool) OrderByClause {
|
||||
return &orderByClauseImpl{expression: expression, ascending: &ascending}
|
||||
}
|
||||
|
||||
func newOrderByNullsFirst(expression Expression, nullsFirst bool) OrderByClause {
|
||||
return &orderByClauseImpl{expression: expression, nullsFirst: &nullsFirst}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package jet
|
||||
|
||||
// MODE computes the most frequent value of the aggregated argument
|
||||
func MODE() *OrderSetAggregateFunc {
|
||||
return newOrderSetAggregateFunction("MODE", nil)
|
||||
}
|
||||
|
||||
// PERCENTILE_CONT computes a value corresponding to the specified fraction within the ordered set of
|
||||
// aggregated argument values. This will interpolate between adjacent input items if needed.
|
||||
func PERCENTILE_CONT(fraction FloatExpression) *OrderSetAggregateFunc {
|
||||
return newOrderSetAggregateFunction("PERCENTILE_CONT", fraction)
|
||||
}
|
||||
|
||||
// PERCENTILE_DISC computes the first value within the ordered set of aggregated argument values whose position
|
||||
// in the ordering equals or exceeds the specified fraction. The aggregated argument must be of a sortable type.
|
||||
func PERCENTILE_DISC(fraction FloatExpression) *OrderSetAggregateFunc {
|
||||
return newOrderSetAggregateFunction("PERCENTILE_DISC", fraction)
|
||||
}
|
||||
|
||||
// OrderSetAggregateFunc implementation of order set aggregate function
|
||||
type OrderSetAggregateFunc struct {
|
||||
name string
|
||||
fraction FloatExpression
|
||||
orderBy Window
|
||||
}
|
||||
|
||||
func newOrderSetAggregateFunction(name string, fraction FloatExpression) *OrderSetAggregateFunc {
|
||||
return &OrderSetAggregateFunc{
|
||||
name: name,
|
||||
fraction: fraction,
|
||||
}
|
||||
}
|
||||
|
||||
// WITHIN_GROUP_ORDER_BY specifies ordered set of aggregated argument values
|
||||
func (p *OrderSetAggregateFunc) WITHIN_GROUP_ORDER_BY(orderBy OrderByClause) Expression {
|
||||
p.orderBy = ORDER_BY(orderBy)
|
||||
return newOrderSetAggregateFuncExpression(*p)
|
||||
}
|
||||
|
||||
func newOrderSetAggregateFuncExpression(aggFunc OrderSetAggregateFunc) *orderSetAggregateFuncExpression {
|
||||
ret := &orderSetAggregateFuncExpression{
|
||||
OrderSetAggregateFunc: aggFunc,
|
||||
}
|
||||
|
||||
ret.ExpressionInterfaceImpl.Parent = ret
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
type orderSetAggregateFuncExpression struct {
|
||||
ExpressionInterfaceImpl
|
||||
OrderSetAggregateFunc
|
||||
}
|
||||
|
||||
func (p *orderSetAggregateFuncExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.WriteString(p.name)
|
||||
|
||||
if p.fraction != nil {
|
||||
wrap(p.fraction).serialize(statement, out, FallTrough(options)...)
|
||||
} else {
|
||||
wrap().serialize(statement, out, FallTrough(options)...)
|
||||
}
|
||||
out.WriteString("WITHIN GROUP")
|
||||
p.orderBy.serialize(statement, out)
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package jet
|
||||
|
||||
// Projection is interface for all projection types. Types that can be part of, for instance SELECT clause.
|
||||
type Projection interface {
|
||||
serializeForProjection(statement StatementType, out *SQLBuilder)
|
||||
fromImpl(subQuery SelectTable) Projection
|
||||
}
|
||||
|
||||
// SerializeForProjection is helper function for serializing projection outside of jet package
|
||||
func SerializeForProjection(projection Projection, statementType StatementType, out *SQLBuilder) {
|
||||
projection.serializeForProjection(statementType, out)
|
||||
}
|
||||
|
||||
// ProjectionList is a redefined type, so that ProjectionList can be used as a Projection.
|
||||
type ProjectionList []Projection
|
||||
|
||||
func (pl ProjectionList) fromImpl(subQuery SelectTable) Projection {
|
||||
newProjectionList := ProjectionList{}
|
||||
|
||||
for _, projection := range pl {
|
||||
newProjectionList = append(newProjectionList, projection.fromImpl(subQuery))
|
||||
}
|
||||
|
||||
return newProjectionList
|
||||
}
|
||||
|
||||
func (pl ProjectionList) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||
SerializeProjectionList(statement, pl, out)
|
||||
}
|
||||
|
||||
// As will create new projection list where each column is wrapped with a new table alias.
|
||||
// tableAlias should be in the form 'name' or 'name.*', or it can be an empty string, which will remove existing table alias.
|
||||
// For instance: If projection list has a column 'Artist.Name', and tableAlias is 'Musician.*', returned projection list will
|
||||
// have a column wrapped in alias 'Musician.Name'. If tableAlias is empty string, it removes existing table alias ('Artist.Name' becomes 'Name').
|
||||
func (pl ProjectionList) As(tableAlias string) ProjectionList {
|
||||
newProjectionList := ProjectionList{}
|
||||
|
||||
for _, projection := range pl {
|
||||
switch p := projection.(type) {
|
||||
case ProjectionList:
|
||||
newProjectionList = append(newProjectionList, p.As(tableAlias))
|
||||
case ColumnList:
|
||||
newProjectionList = append(newProjectionList, p.As(tableAlias))
|
||||
case ColumnExpression:
|
||||
newProjectionList = append(newProjectionList, newAlias(p, joinAlias(tableAlias, p.Name())))
|
||||
case *alias:
|
||||
newAlias := *p
|
||||
_, columnName := extractTableAndColumnName(newAlias.alias)
|
||||
newAlias.alias = joinAlias(tableAlias, columnName)
|
||||
newProjectionList = append(newProjectionList, &newAlias)
|
||||
}
|
||||
}
|
||||
|
||||
return newProjectionList
|
||||
}
|
||||
|
||||
// Except will create new projection list in which columns contained in excluded column names are removed
|
||||
func (pl ProjectionList) Except(toExclude ...Column) ProjectionList {
|
||||
excludedColumnList := UnwidColumnList(toExclude)
|
||||
excludedColumnNames := map[string]bool{}
|
||||
|
||||
for _, excludedColumn := range excludedColumnList {
|
||||
excludedColumnNames[excludedColumn.Name()] = true
|
||||
}
|
||||
|
||||
var ret ProjectionList
|
||||
|
||||
for _, projection := range pl {
|
||||
switch p := projection.(type) {
|
||||
case ProjectionList:
|
||||
ret = append(ret, p.Except(toExclude...))
|
||||
case ColumnExpression:
|
||||
if excludedColumnNames[p.Name()] {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, p)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package jet
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestProjectionAs(t *testing.T) {
|
||||
projectionList := ProjectionList{
|
||||
table1Col3,
|
||||
SUM(table1ColInt).AS("sum"),
|
||||
SUM(table1ColInt).AS("table.sum"),
|
||||
ProjectionList{
|
||||
table1ColBool,
|
||||
AVG(table1ColInt).AS("avg"),
|
||||
AVG(table1ColInt).AS("t.avg"),
|
||||
},
|
||||
ColumnList{table2Col3, table2Col4},
|
||||
}
|
||||
|
||||
aliasedProjectionList := projectionList.As("new_alias.*")
|
||||
|
||||
assertProjectionSerialize(t, aliasedProjectionList,
|
||||
`table1.col3 AS "new_alias.col3",
|
||||
SUM(table1.col_int) AS "new_alias.sum",
|
||||
SUM(table1.col_int) AS "new_alias.sum",
|
||||
table1.col_bool AS "new_alias.col_bool",
|
||||
AVG(table1.col_int) AS "new_alias.avg",
|
||||
AVG(table1.col_int) AS "new_alias.avg",
|
||||
table2.col3 AS "new_alias.col3",
|
||||
table2.col4 AS "new_alias.col4"`)
|
||||
|
||||
aliasedProjectionList = projectionList.As("")
|
||||
|
||||
assertProjectionSerialize(t, aliasedProjectionList,
|
||||
`table1.col3 AS "col3",
|
||||
SUM(table1.col_int) AS "sum",
|
||||
SUM(table1.col_int) AS "sum",
|
||||
table1.col_bool AS "col_bool",
|
||||
AVG(table1.col_int) AS "avg",
|
||||
AVG(table1.col_int) AS "avg",
|
||||
table2.col3 AS "col3",
|
||||
table2.col4 AS "col4"`)
|
||||
|
||||
subQueryProjections := projectionList.fromImpl(NewSelectTable(nil, "subQuery", nil))
|
||||
|
||||
assertProjectionSerialize(t, subQueryProjections,
|
||||
`"subQuery"."table1.col3" AS "table1.col3",
|
||||
"subQuery".sum AS "sum",
|
||||
"subQuery"."table.sum" AS "table.sum",
|
||||
"subQuery"."table1.col_bool" AS "table1.col_bool",
|
||||
"subQuery".avg AS "avg",
|
||||
"subQuery"."t.avg" AS "t.avg",
|
||||
"subQuery"."table2.col3" AS "table2.col3",
|
||||
"subQuery"."table2.col4" AS "table2.col4"`)
|
||||
|
||||
aliasedSubQueryProjectionList := subQueryProjections.(ProjectionList).As("subAlias")
|
||||
|
||||
assertProjectionSerialize(t, aliasedSubQueryProjectionList,
|
||||
`"subQuery"."table1.col3" AS "subAlias.col3",
|
||||
"subQuery".sum AS "subAlias.sum",
|
||||
"subQuery"."table.sum" AS "subAlias.sum",
|
||||
"subQuery"."table1.col_bool" AS "subAlias.col_bool",
|
||||
"subQuery".avg AS "subAlias.avg",
|
||||
"subQuery"."t.avg" AS "subAlias.avg",
|
||||
"subQuery"."table2.col3" AS "subAlias.col3",
|
||||
"subQuery"."table2.col4" AS "subAlias.col4"`)
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package jet
|
||||
|
||||
// Range Expression is interface for date range types
|
||||
type Range[T Expression] interface {
|
||||
Expression
|
||||
|
||||
EQ(rhs Range[T]) BoolExpression
|
||||
NOT_EQ(rhs Range[T]) BoolExpression
|
||||
|
||||
LT(rhs Range[T]) BoolExpression
|
||||
LT_EQ(rhs Range[T]) BoolExpression
|
||||
GT(rhs Range[T]) BoolExpression
|
||||
GT_EQ(rhs Range[T]) BoolExpression
|
||||
|
||||
CONTAINS(rhs T) BoolExpression
|
||||
CONTAINS_RANGE(rhs Range[T]) BoolExpression
|
||||
OVERLAP(rhs Range[T]) BoolExpression
|
||||
UNION(rhs Range[T]) Range[T]
|
||||
INTERSECTION(rhs Range[T]) Range[T]
|
||||
DIFFERENCE(rhs Range[T]) Range[T]
|
||||
|
||||
UPPER_BOUND() T
|
||||
LOWER_BOUND() T
|
||||
IS_EMPTY() BoolExpression
|
||||
LOWER_INC() BoolExpression
|
||||
UPPER_INC() BoolExpression
|
||||
LOWER_INF() BoolExpression
|
||||
UPPER_INF() BoolExpression
|
||||
}
|
||||
|
||||
type rangeInterfaceImpl[T Expression] struct {
|
||||
parent Range[T]
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) EQ(rhs Range[T]) BoolExpression {
|
||||
return Eq(r.parent, rhs)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) NOT_EQ(rhs Range[T]) BoolExpression {
|
||||
return NotEq(r.parent, rhs)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) LT(rhs Range[T]) BoolExpression {
|
||||
return Lt(r.parent, rhs)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) LT_EQ(rhs Range[T]) BoolExpression {
|
||||
return LtEq(r.parent, rhs)
|
||||
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) GT(rhs Range[T]) BoolExpression {
|
||||
return Gt(r.parent, rhs)
|
||||
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) GT_EQ(rhs Range[T]) BoolExpression {
|
||||
return GtEq(r.parent, rhs)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) CONTAINS(rhs T) BoolExpression {
|
||||
return Contains(r.parent, rhs)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) CONTAINS_RANGE(rhs Range[T]) BoolExpression {
|
||||
return Contains(r.parent, rhs)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) OVERLAP(rhs Range[T]) BoolExpression {
|
||||
return Overlap(r.parent, rhs)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) UNION(rhs Range[T]) Range[T] {
|
||||
return RangeExp[T](Add(r.parent, rhs))
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) INTERSECTION(rhs Range[T]) Range[T] {
|
||||
return RangeExp[T](Mul(r.parent, rhs))
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) DIFFERENCE(rhs Range[T]) Range[T] {
|
||||
return RangeExp[T](Sub(r.parent, rhs))
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) UPPER_BOUND() T {
|
||||
return UPPER_BOUND(r.parent)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) LOWER_BOUND() T {
|
||||
return LOWER_BOUND(r.parent)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) IS_EMPTY() BoolExpression {
|
||||
return IS_EMPTY(r.parent)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) LOWER_INC() BoolExpression {
|
||||
return LOWER_INC(r.parent)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) UPPER_INC() BoolExpression {
|
||||
return UPPER_INC(r.parent)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) LOWER_INF() BoolExpression {
|
||||
return LOWER_INF(r.parent)
|
||||
}
|
||||
|
||||
func (r *rangeInterfaceImpl[T]) UPPER_INF() BoolExpression {
|
||||
return UPPER_INF(r.parent)
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type rangeExpressionWrapper[T Expression] struct {
|
||||
rangeInterfaceImpl[T]
|
||||
Expression
|
||||
}
|
||||
|
||||
func newRangeExpressionWrap[T Expression](expression Expression) Range[T] {
|
||||
rangeExpressionWrap := rangeExpressionWrapper[T]{Expression: expression}
|
||||
rangeExpressionWrap.rangeInterfaceImpl.parent = &rangeExpressionWrap
|
||||
return &rangeExpressionWrap
|
||||
}
|
||||
|
||||
// RangeExp is range expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as range expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func RangeExp[T Expression](expression Expression) Range[T] {
|
||||
return newRangeExpressionWrap[T](expression)
|
||||
}
|
||||
|
||||
// different range expression wrappers
|
||||
var (
|
||||
Int4RangeExp = RangeExp[Int4Expression]
|
||||
Int8RangeExp = RangeExp[Int8Expression]
|
||||
NumRangeExp = RangeExp[NumericExpression]
|
||||
DateRangeExp = RangeExp[DateExpression]
|
||||
TsRangeExp = RangeExp[TimestampExpression]
|
||||
TstzRangeExp = RangeExp[TimestampzExpression]
|
||||
)
|
||||
@@ -1,63 +0,0 @@
|
||||
package jet
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRangeExpressionEQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.EQ(table2ColRange), "(table1.col_range = table2.col_range)")
|
||||
assertClauseSerialize(t, table1ColRange.EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range = int8range($1, $2, $3))", int8(1), int8(4), "[)")
|
||||
}
|
||||
|
||||
func TestRangeExpressionNOT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.NOT_EQ(table2ColRange), "(table1.col_range != table2.col_range)")
|
||||
assertClauseSerialize(t, table1ColRange.NOT_EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range != int8range($1, $2, $3))", int8(1), int8(4), "[)")
|
||||
}
|
||||
|
||||
func TestRangeExpressionLT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.LT(table2ColRange), "(table1.col_range < table2.col_range)")
|
||||
assertClauseSerialize(t, table1ColRange.LT(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range < int8range($1, $2, $3))", int8(1), int8(4), "[)")
|
||||
}
|
||||
|
||||
func TestRangeExpressionLT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.LT_EQ(table2ColRange), "(table1.col_range <= table2.col_range)")
|
||||
assertClauseSerialize(t, table1ColRange.LT_EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range <= int8range($1, $2, $3))", int8(1), int8(4), "[)")
|
||||
}
|
||||
|
||||
func TestRangeExpressionGT(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.GT(table2ColRange), "(table1.col_range > table2.col_range)")
|
||||
assertClauseSerialize(t, table1ColRange.GT(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range > int8range($1, $2, $3))", int8(1), int8(4), "[)")
|
||||
}
|
||||
|
||||
func TestRangeExpressionGT_EQ(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.GT_EQ(table2ColRange), "(table1.col_range >= table2.col_range)")
|
||||
assertClauseSerialize(t, table1ColRange.GT_EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range >= int8range($1, $2, $3))", int8(1), int8(4), "[)")
|
||||
}
|
||||
|
||||
func TestRangeExpressionCONTAINS_RANGE(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.CONTAINS_RANGE(table2ColRange), "(table1.col_range @> table2.col_range)")
|
||||
assertClauseSerialize(t, table1ColRange.CONTAINS_RANGE(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range @> int8range($1, $2, $3))", int8(1), int8(4), "[)")
|
||||
}
|
||||
|
||||
func TestRangeExpressionCONTAINS(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.CONTAINS(table2Col3), "(table1.col_range @> table2.col3)")
|
||||
assertClauseSerialize(t, table1ColRange.CONTAINS(Int8(1)), "(table1.col_range @> $1)", int8(1))
|
||||
}
|
||||
|
||||
func TestRangeExpressionOVERLAP(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.OVERLAP(table2ColRange), "(table1.col_range && table2.col_range)")
|
||||
assertClauseSerialize(t, table1ColRange.OVERLAP(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range && int8range($1, $2, $3))", int8(1), int8(4), "[)")
|
||||
}
|
||||
|
||||
func TestRangeExpressionUNION(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.UNION(table2ColRange), "(table1.col_range + table2.col_range)")
|
||||
assertClauseSerialize(t, table1ColRange.UNION(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range + int8range($1, $2, $3))", int8(1), int8(4), "[)")
|
||||
}
|
||||
|
||||
func TestRangeExpressionINTERSECTION(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.INTERSECTION(table2ColRange), "(table1.col_range * table2.col_range)")
|
||||
assertClauseSerialize(t, table1ColRange.INTERSECTION(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range * int8range($1, $2, $3))", int8(1), int8(4), "[)")
|
||||
}
|
||||
|
||||
func TestRangeExpressionDIFFERENCE(t *testing.T) {
|
||||
assertClauseSerialize(t, table1ColRange.DIFFERENCE(table2ColRange), "(table1.col_range - table2.col_range)")
|
||||
assertClauseSerialize(t, table1ColRange.DIFFERENCE(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range - int8range($1, $2, $3))", int8(1), int8(4), "[)")
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package jet
|
||||
|
||||
type rawStatementImpl struct {
|
||||
serializerStatementInterfaceImpl
|
||||
|
||||
RawQuery string
|
||||
NamedArguments map[string]interface{}
|
||||
}
|
||||
|
||||
// RawStatement creates new sql statements from raw query and optional map of named arguments
|
||||
func RawStatement(dialect Dialect, rawQuery string, namedArgument ...map[string]interface{}) SerializerStatement {
|
||||
newRawStatement := rawStatementImpl{
|
||||
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
|
||||
dialect: dialect,
|
||||
statementType: "",
|
||||
parent: nil,
|
||||
},
|
||||
RawQuery: rawQuery,
|
||||
}
|
||||
|
||||
if len(namedArgument) > 0 {
|
||||
newRawStatement.NamedArguments = namedArgument[0]
|
||||
}
|
||||
|
||||
newRawStatement.parent = &newRawStatement
|
||||
|
||||
return &newRawStatement
|
||||
}
|
||||
|
||||
func (s *rawStatementImpl) projections() ProjectionList {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *rawStatementImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if !contains(options, NoWrap) {
|
||||
out.WriteString("(")
|
||||
out.IncreaseIdent()
|
||||
}
|
||||
|
||||
out.insertRawQuery(s.RawQuery, s.NamedArguments)
|
||||
|
||||
if !contains(options, NoWrap) {
|
||||
out.DecreaseIdent()
|
||||
out.NewLine()
|
||||
out.WriteString(")")
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package jet
|
||||
|
||||
// RowExpression interface
|
||||
type RowExpression interface {
|
||||
Expression
|
||||
HasProjections
|
||||
|
||||
EQ(rhs RowExpression) BoolExpression
|
||||
NOT_EQ(rhs RowExpression) BoolExpression
|
||||
IS_DISTINCT_FROM(rhs RowExpression) BoolExpression
|
||||
IS_NOT_DISTINCT_FROM(rhs RowExpression) BoolExpression
|
||||
|
||||
LT(rhs RowExpression) BoolExpression
|
||||
LT_EQ(rhs RowExpression) BoolExpression
|
||||
GT(rhs RowExpression) BoolExpression
|
||||
GT_EQ(rhs RowExpression) BoolExpression
|
||||
}
|
||||
|
||||
type rowInterfaceImpl struct {
|
||||
parent Expression
|
||||
dialect Dialect
|
||||
elemCount int
|
||||
}
|
||||
|
||||
func (n *rowInterfaceImpl) EQ(rhs RowExpression) BoolExpression {
|
||||
return Eq(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *rowInterfaceImpl) NOT_EQ(rhs RowExpression) BoolExpression {
|
||||
return NotEq(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *rowInterfaceImpl) IS_DISTINCT_FROM(rhs RowExpression) BoolExpression {
|
||||
return IsDistinctFrom(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *rowInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs RowExpression) BoolExpression {
|
||||
return IsNotDistinctFrom(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *rowInterfaceImpl) GT(rhs RowExpression) BoolExpression {
|
||||
return Gt(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *rowInterfaceImpl) GT_EQ(rhs RowExpression) BoolExpression {
|
||||
return GtEq(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *rowInterfaceImpl) LT(rhs RowExpression) BoolExpression {
|
||||
return Lt(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *rowInterfaceImpl) LT_EQ(rhs RowExpression) BoolExpression {
|
||||
return LtEq(n.parent, rhs)
|
||||
}
|
||||
|
||||
func (n *rowInterfaceImpl) projections() ProjectionList {
|
||||
var ret ProjectionList
|
||||
|
||||
for i := 0; i < n.elemCount; i++ {
|
||||
rowColumn := NewColumnImpl(n.dialect.ValuesDefaultColumnName(i), "", nil)
|
||||
ret = append(ret, &rowColumn)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// ---------------------------------------------------//
|
||||
type rowExpressionWrapper struct {
|
||||
rowInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newRowExpression(name string, dialect Dialect, expressions ...Expression) RowExpression {
|
||||
ret := &rowExpressionWrapper{}
|
||||
ret.rowInterfaceImpl.parent = ret
|
||||
|
||||
ret.Expression = NewFunc(name, expressions, ret)
|
||||
ret.dialect = dialect
|
||||
ret.elemCount = len(expressions)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
|
||||
func ROW(dialect Dialect, expressions ...Expression) RowExpression {
|
||||
return newRowExpression("ROW", dialect, expressions...)
|
||||
}
|
||||
|
||||
// WRAP creates row expressions without ROW keyword `( expression1, expression2, ... )`.
|
||||
func WRAP(dialect Dialect, expressions ...Expression) RowExpression {
|
||||
return newRowExpression("", dialect, expressions...)
|
||||
}
|
||||
|
||||
// RowExp serves as a wrapper for an arbitrary expression, treating it as a row expression.
|
||||
// This enables the Go compiler to interpret any expression as a row expression
|
||||
// Note: This does not modify the generated SQL builder output by adding a SQL CAST operation.
|
||||
func RowExp(expression Expression) RowExpression {
|
||||
rowExpressionWrap := rowExpressionWrapper{Expression: expression}
|
||||
rowExpressionWrap.rowInterfaceImpl.parent = &rowExpressionWrap
|
||||
return &rowExpressionWrap
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package jet
|
||||
|
||||
// RowLock is interface for SELECT statement row lock types
|
||||
type RowLock interface {
|
||||
Serializer
|
||||
|
||||
OF(...Table) RowLock
|
||||
NOWAIT() RowLock
|
||||
SKIP_LOCKED() RowLock
|
||||
}
|
||||
|
||||
type selectLockImpl struct {
|
||||
lockStrength string
|
||||
of []Table
|
||||
noWait, skipLocked bool
|
||||
}
|
||||
|
||||
// NewRowLock creates new RowLock
|
||||
func NewRowLock(name string) func() RowLock {
|
||||
return func() RowLock {
|
||||
return newSelectLock(name)
|
||||
}
|
||||
}
|
||||
|
||||
func newSelectLock(lockStrength string) *selectLockImpl {
|
||||
return &selectLockImpl{lockStrength: lockStrength}
|
||||
}
|
||||
|
||||
func (s *selectLockImpl) OF(tables ...Table) RowLock {
|
||||
s.of = tables
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *selectLockImpl) NOWAIT() RowLock {
|
||||
s.noWait = true
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *selectLockImpl) SKIP_LOCKED() RowLock {
|
||||
s.skipLocked = true
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *selectLockImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.WriteString(s.lockStrength)
|
||||
|
||||
if len(s.of) > 0 {
|
||||
out.WriteString("OF")
|
||||
|
||||
for i, of := range s.of {
|
||||
if i > 0 {
|
||||
out.WriteString(", ")
|
||||
}
|
||||
|
||||
table := of.Alias()
|
||||
if table == "" {
|
||||
table = of.TableName()
|
||||
}
|
||||
|
||||
out.WriteIdentifier(table)
|
||||
}
|
||||
}
|
||||
|
||||
if s.noWait {
|
||||
out.WriteString("NOWAIT")
|
||||
}
|
||||
|
||||
if s.skipLocked {
|
||||
out.WriteString("SKIP LOCKED")
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package jet
|
||||
|
||||
// SelectTable is interface for SELECT sub-queries
|
||||
type SelectTable interface {
|
||||
SerializerHasProjections
|
||||
Alias() string
|
||||
AllColumns() ProjectionList
|
||||
}
|
||||
|
||||
type selectTableImpl struct {
|
||||
Statement SerializerHasProjections
|
||||
alias string
|
||||
columnAliases []ColumnExpression
|
||||
}
|
||||
|
||||
// NewSelectTable func
|
||||
func NewSelectTable(selectStmt SerializerHasProjections, alias string, columnAliases []ColumnExpression) selectTableImpl {
|
||||
selectTable := selectTableImpl{
|
||||
Statement: selectStmt,
|
||||
alias: alias,
|
||||
columnAliases: columnAliases,
|
||||
}
|
||||
|
||||
for _, column := range selectTable.columnAliases {
|
||||
column.setSubQuery(selectTable)
|
||||
}
|
||||
|
||||
return selectTable
|
||||
}
|
||||
|
||||
func (s selectTableImpl) projections() ProjectionList {
|
||||
return s.Statement.projections()
|
||||
}
|
||||
|
||||
func (s selectTableImpl) Alias() string {
|
||||
return s.alias
|
||||
}
|
||||
|
||||
func (s selectTableImpl) AllColumns() ProjectionList {
|
||||
if len(s.columnAliases) > 0 {
|
||||
return ColumnListToProjectionList(s.columnAliases)
|
||||
}
|
||||
|
||||
projectionList := s.projections().fromImpl(s)
|
||||
return projectionList.(ProjectionList)
|
||||
}
|
||||
|
||||
func (s selectTableImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
s.Statement.serialize(statement, out)
|
||||
|
||||
out.WriteString("AS")
|
||||
out.WriteIdentifier(s.alias)
|
||||
|
||||
if len(s.columnAliases) > 0 {
|
||||
out.WriteByte('(')
|
||||
SerializeColumnExpressionNames(s.columnAliases, out)
|
||||
out.WriteByte(')')
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
type lateralImpl struct {
|
||||
selectTableImpl
|
||||
}
|
||||
|
||||
// NewLateral creates new lateral expression from select statement with alias
|
||||
func NewLateral(selectStmt SerializerStatement, alias string) SelectTable {
|
||||
return lateralImpl{selectTableImpl: NewSelectTable(selectStmt, alias, nil)}
|
||||
}
|
||||
|
||||
func (s lateralImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.WriteString("LATERAL")
|
||||
s.Statement.serialize(statement, out)
|
||||
|
||||
out.WriteString("AS")
|
||||
out.WriteIdentifier(s.alias)
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package jet
|
||||
|
||||
// SerializeOption type
|
||||
type SerializeOption int
|
||||
|
||||
// Serialize options
|
||||
const (
|
||||
NoWrap SerializeOption = iota
|
||||
SkipNewLine
|
||||
Ident
|
||||
|
||||
fallTroughOptions // fall trough options
|
||||
|
||||
ShortName
|
||||
)
|
||||
|
||||
// WithFallTrough extends existing serialize options with additional
|
||||
func (s SerializeOption) WithFallTrough(options []SerializeOption) []SerializeOption {
|
||||
return append(FallTrough(options), s)
|
||||
}
|
||||
|
||||
// StatementType is type of the SQL statement
|
||||
type StatementType string
|
||||
|
||||
// Statement types
|
||||
const (
|
||||
SelectStatementType StatementType = "SELECT"
|
||||
InsertStatementType StatementType = "INSERT"
|
||||
UpdateStatementType StatementType = "UPDATE"
|
||||
DeleteStatementType StatementType = "DELETE"
|
||||
SetStatementType StatementType = "SET"
|
||||
LockStatementType StatementType = "LOCK"
|
||||
UnLockStatementType StatementType = "UNLOCK"
|
||||
WithStatementType StatementType = "WITH"
|
||||
)
|
||||
|
||||
// Serializer interface
|
||||
type Serializer interface {
|
||||
serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption)
|
||||
}
|
||||
|
||||
// Serialize func
|
||||
func Serialize(exp Serializer, statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
exp.serialize(statementType, out, options...)
|
||||
}
|
||||
|
||||
func SerializeForOrderBy(exp Expression, statementType StatementType, out *SQLBuilder) {
|
||||
exp.serializeForOrderBy(statementType, out)
|
||||
}
|
||||
|
||||
func contains(options []SerializeOption, option SerializeOption) bool {
|
||||
for _, opt := range options {
|
||||
if opt == option {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// FallTrough filters fall-trough options from the list
|
||||
func FallTrough(options []SerializeOption) []SerializeOption {
|
||||
var ret []SerializeOption
|
||||
|
||||
for _, option := range options {
|
||||
if option > fallTroughOptions {
|
||||
ret = append(ret, option)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// ListSerializer serializes list of serializers with separator
|
||||
type ListSerializer struct {
|
||||
Serializers []Serializer
|
||||
Separator string
|
||||
}
|
||||
|
||||
func (s ListSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
for i, ser := range s.Serializers {
|
||||
if i > 0 {
|
||||
out.WriteString(s.Separator)
|
||||
}
|
||||
ser.serialize(statement, out, FallTrough(options)...)
|
||||
}
|
||||
}
|
||||
|
||||
// NewSerializerClauseImpl is constructor for Seralizer with list of clauses
|
||||
func NewSerializerClauseImpl(clauses ...Clause) Serializer {
|
||||
return &serializerImpl{Clauses: clauses}
|
||||
}
|
||||
|
||||
type serializerImpl struct {
|
||||
Clauses []Clause
|
||||
}
|
||||
|
||||
func (s serializerImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
for _, clause := range s.Clauses {
|
||||
clause.Serialize(statement, out, FallTrough(options)...)
|
||||
}
|
||||
}
|
||||
|
||||
// Token can be used to construct complex custom expressions
|
||||
type Token string
|
||||
|
||||
func (t Token) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
out.WriteString(string(t))
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"github.com/go-jet/jet/v2/internal/3rdparty/pq"
|
||||
"github.com/go-jet/jet/v2/internal/utils/is"
|
||||
"github.com/google/uuid"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// SQLBuilder generates output SQL
|
||||
type SQLBuilder struct {
|
||||
Dialect Dialect
|
||||
Buff bytes.Buffer
|
||||
Args []interface{}
|
||||
|
||||
lastChar byte
|
||||
ident int
|
||||
|
||||
Debug bool
|
||||
}
|
||||
|
||||
const tabSize = 4
|
||||
const defaultIdent = 5
|
||||
|
||||
// IncreaseIdent adds ident or defaultIdent number of spaces to each new line
|
||||
func (s *SQLBuilder) IncreaseIdent(ident ...int) {
|
||||
if len(ident) > 0 {
|
||||
s.ident += ident[0]
|
||||
} else {
|
||||
s.ident += defaultIdent
|
||||
}
|
||||
}
|
||||
|
||||
// DecreaseIdent removes ident or defaultIdent number of spaces for each new line
|
||||
func (s *SQLBuilder) DecreaseIdent(ident ...int) {
|
||||
toDecrease := defaultIdent
|
||||
|
||||
if len(ident) > 0 {
|
||||
toDecrease = ident[0]
|
||||
}
|
||||
|
||||
if s.ident < toDecrease {
|
||||
s.ident = 0
|
||||
}
|
||||
|
||||
s.ident -= toDecrease
|
||||
}
|
||||
|
||||
// WriteProjections func
|
||||
func (s *SQLBuilder) WriteProjections(statement StatementType, projections []Projection) {
|
||||
s.IncreaseIdent()
|
||||
SerializeProjectionList(statement, projections, s)
|
||||
s.DecreaseIdent()
|
||||
}
|
||||
|
||||
// NewLine adds new line to output SQL
|
||||
func (s *SQLBuilder) NewLine() {
|
||||
s.write([]byte{'\n'})
|
||||
s.write(bytes.Repeat([]byte{' '}, s.ident))
|
||||
}
|
||||
|
||||
func (s *SQLBuilder) write(data []byte) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !isPreSeparator(s.lastChar) && !isPostSeparator(data[0]) && s.Buff.Len() > 0 {
|
||||
s.Buff.WriteByte(' ')
|
||||
}
|
||||
|
||||
s.Buff.Write(data)
|
||||
s.lastChar = data[len(data)-1]
|
||||
}
|
||||
|
||||
func isPreSeparator(b byte) bool {
|
||||
return b == ' ' || b == '.' || b == ',' || b == '(' || b == '\n' || b == ':'
|
||||
}
|
||||
|
||||
func isPostSeparator(b byte) bool {
|
||||
return b == ' ' || b == '.' || b == ',' || b == ')' || b == '\n' || b == ':'
|
||||
}
|
||||
|
||||
// WriteAlias is used to add alias to output SQL
|
||||
func (s *SQLBuilder) WriteAlias(str string) {
|
||||
aliasQuoteChar := string(s.Dialect.AliasQuoteChar())
|
||||
s.WriteString(aliasQuoteChar + str + aliasQuoteChar)
|
||||
}
|
||||
|
||||
// WriteString writes sting to output SQL
|
||||
func (s *SQLBuilder) WriteString(str string) {
|
||||
s.write([]byte(str))
|
||||
}
|
||||
|
||||
// WriteIdentifier adds identifier to output SQL
|
||||
func (s *SQLBuilder) WriteIdentifier(name string, alwaysQuote ...bool) {
|
||||
if s.shouldQuote(name, alwaysQuote...) {
|
||||
identQuoteChar := string(s.Dialect.IdentifierQuoteChar())
|
||||
s.WriteString(identQuoteChar + name + identQuoteChar)
|
||||
} else {
|
||||
s.WriteString(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SQLBuilder) shouldQuote(name string, alwaysQuote ...bool) bool {
|
||||
return s.Dialect.IsReservedWord(name) || shouldQuoteIdentifier(name) || len(alwaysQuote) > 0
|
||||
}
|
||||
|
||||
// WriteByte writes byte to output SQL
|
||||
func (s *SQLBuilder) WriteByte(b byte) {
|
||||
s.write([]byte{b})
|
||||
}
|
||||
|
||||
func (s *SQLBuilder) finalize() (string, []interface{}) {
|
||||
return s.Buff.String() + ";\n", s.Args
|
||||
}
|
||||
|
||||
func (s *SQLBuilder) insertConstantArgument(arg interface{}) {
|
||||
s.WriteString(argToString(arg))
|
||||
}
|
||||
|
||||
func (s *SQLBuilder) insertParametrizedArgument(arg interface{}) {
|
||||
if s.Debug {
|
||||
s.insertConstantArgument(arg)
|
||||
return
|
||||
}
|
||||
|
||||
s.Args = append(s.Args, arg)
|
||||
argPlaceholder := s.Dialect.ArgumentPlaceholder()(len(s.Args))
|
||||
|
||||
s.WriteString(argPlaceholder)
|
||||
}
|
||||
|
||||
func (s *SQLBuilder) insertRawQuery(raw string, namedArg map[string]interface{}) {
|
||||
type namedArgumentPosition struct {
|
||||
Name string
|
||||
Value interface{}
|
||||
Position int
|
||||
}
|
||||
|
||||
var namedArgumentPositions []namedArgumentPosition
|
||||
|
||||
for namedArg, value := range namedArg {
|
||||
rawCopy := raw
|
||||
rawIndex := 0
|
||||
exists := false
|
||||
|
||||
// one named argument can occur multiple times inside raw string
|
||||
for {
|
||||
index := strings.Index(rawCopy, namedArg)
|
||||
if index == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
exists = true
|
||||
namedArgumentPositions = append(namedArgumentPositions, namedArgumentPosition{
|
||||
Name: namedArg,
|
||||
Value: value,
|
||||
Position: rawIndex + index,
|
||||
})
|
||||
|
||||
rawCopy = rawCopy[index+len(namedArg):]
|
||||
rawIndex += index + len(namedArg)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
panic("jet: named argument '" + namedArg + "' does not appear in raw query")
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(namedArgumentPositions, func(i, j int) bool {
|
||||
return namedArgumentPositions[i].Position < namedArgumentPositions[j].Position
|
||||
})
|
||||
|
||||
for _, namedArgumentPos := range namedArgumentPositions {
|
||||
// if named argument does not exists in raw string do not add argument to the list of arguments
|
||||
// It can happen if the same argument occurs multiple times in postgres query.
|
||||
if !strings.Contains(raw, namedArgumentPos.Name) {
|
||||
continue
|
||||
}
|
||||
s.Args = append(s.Args, namedArgumentPos.Value)
|
||||
currentArgNum := len(s.Args)
|
||||
|
||||
placeholder := s.Dialect.ArgumentPlaceholder()(currentArgNum)
|
||||
// if placeholder is not unique identifier ($1, $2, etc..), we will replace just one occurrence of the argument
|
||||
toReplace := -1 // all occurrences
|
||||
if placeholder == "?" {
|
||||
toReplace = 1 // just one occurrence
|
||||
}
|
||||
|
||||
if s.Debug {
|
||||
placeholder = argToString(namedArgumentPos.Value)
|
||||
}
|
||||
|
||||
raw = strings.Replace(raw, namedArgumentPos.Name, placeholder, toReplace)
|
||||
}
|
||||
|
||||
s.WriteString(raw)
|
||||
}
|
||||
|
||||
func argToString(value interface{}) string {
|
||||
if is.Nil(value) {
|
||||
return "NULL"
|
||||
}
|
||||
|
||||
switch bindVal := value.(type) {
|
||||
case bool:
|
||||
if bindVal {
|
||||
return "TRUE"
|
||||
}
|
||||
return "FALSE"
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
return integerTypesToString(bindVal)
|
||||
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(bindVal), 'f', -1, 64)
|
||||
case float64:
|
||||
return strconv.FormatFloat(float64(bindVal), 'f', -1, 64)
|
||||
|
||||
case string:
|
||||
return stringQuote(bindVal)
|
||||
case []byte:
|
||||
return stringQuote(string(bindVal))
|
||||
case uuid.UUID:
|
||||
return stringQuote(bindVal.String())
|
||||
case time.Time:
|
||||
return stringQuote(string(pq.FormatTimestamp(bindVal)))
|
||||
default:
|
||||
if strBindValue, ok := bindVal.(fmt.Stringer); ok {
|
||||
return stringQuote(strBindValue.String())
|
||||
}
|
||||
|
||||
if valuer, ok := bindVal.(driver.Valuer); ok {
|
||||
val, err := valuer.Value()
|
||||
|
||||
if err != nil {
|
||||
// If valuer for some reason returns an error, we return error string representation.
|
||||
// This is fine because argToString is called only from DebugSQL, and DebugSQL shouldn't be used in production.
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return argToString(val)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("jet: %s type can not be used as SQL query parameter", reflect.TypeOf(value).String()))
|
||||
}
|
||||
}
|
||||
|
||||
func integerTypesToString(value interface{}) string {
|
||||
switch bindVal := value.(type) {
|
||||
case int:
|
||||
return strconv.FormatInt(int64(bindVal), 10)
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(bindVal), 10)
|
||||
case int8:
|
||||
return strconv.FormatInt(int64(bindVal), 10)
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(bindVal), 10)
|
||||
case int16:
|
||||
return strconv.FormatInt(int64(bindVal), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(bindVal), 10)
|
||||
case int32:
|
||||
return strconv.FormatInt(int64(bindVal), 10)
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(bindVal), 10)
|
||||
case int64:
|
||||
return strconv.FormatInt(bindVal, 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(bindVal, 10)
|
||||
}
|
||||
panic("jet: Unsupported integer type: " + reflect.TypeOf(value).String())
|
||||
}
|
||||
|
||||
func shouldQuoteIdentifier(identifier string) bool {
|
||||
_, err := strconv.ParseInt(identifier, 10, 64)
|
||||
|
||||
if err == nil { // if it is a number we should quote it
|
||||
return true
|
||||
}
|
||||
|
||||
// check if contains non ascii characters
|
||||
for _, c := range identifier {
|
||||
if unicode.IsNumber(c) || c == '_' {
|
||||
continue
|
||||
}
|
||||
if c > unicode.MaxASCII || !unicode.IsLetter(c) || unicode.IsUpper(c) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func stringQuote(value string) string {
|
||||
return `'` + strings.Replace(value, "'", "''", -1) + `'`
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestArgToString(t *testing.T) {
|
||||
require.Equal(t, argToString(true), "TRUE")
|
||||
require.Equal(t, argToString(false), "FALSE")
|
||||
|
||||
require.Equal(t, argToString(int(-32)), "-32")
|
||||
require.Equal(t, argToString(uint(32)), "32")
|
||||
require.Equal(t, argToString(int8(-43)), "-43")
|
||||
require.Equal(t, argToString(uint8(43)), "43")
|
||||
require.Equal(t, argToString(int16(-54)), "-54")
|
||||
require.Equal(t, argToString(uint16(54)), "54")
|
||||
require.Equal(t, argToString(int32(-65)), "-65")
|
||||
require.Equal(t, argToString(uint32(65)), "65")
|
||||
require.Equal(t, argToString(int64(-64)), "-64")
|
||||
require.Equal(t, argToString(uint64(64)), "64")
|
||||
require.Equal(t, argToString(float32(2.0)), "2")
|
||||
require.Equal(t, argToString(float64(1.11)), "1.11")
|
||||
|
||||
require.Equal(t, argToString("john"), "'john'")
|
||||
require.Equal(t, argToString("It's text"), "'It''s text'")
|
||||
require.Equal(t, argToString([]byte("john")), "'john'")
|
||||
require.Equal(t, argToString(uuid.MustParse("b68dbff4-a87d-11e9-a7f2-98ded00c39c6")), "'b68dbff4-a87d-11e9-a7f2-98ded00c39c6'")
|
||||
|
||||
time, err := time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", "Mon Jan 2 15:04:05 -0700 MST 2006")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, argToString(time), "'2006-01-02 15:04:05-07:00'")
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
require.Equal(t, recover().(string), "jet: map[string]bool type can not be used as SQL query parameter")
|
||||
}()
|
||||
|
||||
argToString(map[string]bool{})
|
||||
}()
|
||||
}
|
||||
|
||||
func TestFallTrough(t *testing.T) {
|
||||
require.Equal(t, FallTrough([]SerializeOption{ShortName}), []SerializeOption{ShortName})
|
||||
require.Equal(t, FallTrough([]SerializeOption{SkipNewLine}), []SerializeOption(nil))
|
||||
require.Equal(t, FallTrough([]SerializeOption{ShortName, SkipNewLine}), []SerializeOption{ShortName})
|
||||
}
|
||||
|
||||
func TestShouldQuote(t *testing.T) {
|
||||
require.Equal(t, shouldQuoteIdentifier("123"), true)
|
||||
require.Equal(t, shouldQuoteIdentifier("123.235"), true)
|
||||
require.Equal(t, shouldQuoteIdentifier("abc123"), false)
|
||||
require.Equal(t, shouldQuoteIdentifier("abc.123"), true)
|
||||
require.Equal(t, shouldQuoteIdentifier("abc_123"), false)
|
||||
require.Equal(t, shouldQuoteIdentifier("Abc_123"), true)
|
||||
require.Equal(t, shouldQuoteIdentifier("DŽƜĐǶ"), true)
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"github.com/go-jet/jet/v2/qrm"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Statement is common interface for all statements(SELECT, INSERT, UPDATE, DELETE, LOCK)
|
||||
type Statement interface {
|
||||
// Sql returns parametrized sql query with list of arguments.
|
||||
Sql() (query string, args []interface{})
|
||||
// DebugSql returns debug query where every parametrized placeholder is replaced with its argument string representation.
|
||||
// Do not use it in production. Use it only for debug purposes.
|
||||
DebugSql() (query string)
|
||||
// Query executes statement over database connection/transaction db and stores row results in destination.
|
||||
// Destination can be either pointer to struct or pointer to a slice.
|
||||
// If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows.
|
||||
Query(db qrm.Queryable, destination interface{}) error
|
||||
// QueryContext executes statement with a context over database connection/transaction db and stores row result in destination.
|
||||
// Destination can be either pointer to struct or pointer to a slice.
|
||||
// If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows.
|
||||
QueryContext(ctx context.Context, db qrm.Queryable, destination interface{}) error
|
||||
// Exec executes statement over db connection/transaction without returning any rows.
|
||||
Exec(db qrm.Executable) (sql.Result, error)
|
||||
// ExecContext executes statement with context over db connection/transaction without returning any rows.
|
||||
ExecContext(ctx context.Context, db qrm.Executable) (sql.Result, error)
|
||||
// Rows executes statements over db connection/transaction and returns rows
|
||||
Rows(ctx context.Context, db qrm.Queryable) (*Rows, error)
|
||||
}
|
||||
|
||||
// Rows wraps sql.Rows type with a support for query result mapping
|
||||
type Rows struct {
|
||||
*sql.Rows
|
||||
|
||||
scanContext *qrm.ScanContext
|
||||
}
|
||||
|
||||
// Scan will map the Row values into struct destination
|
||||
func (r *Rows) Scan(destination interface{}) error {
|
||||
return qrm.ScanOneRowToDest(r.scanContext, r.Rows, destination)
|
||||
}
|
||||
|
||||
// SerializerStatement interface
|
||||
type SerializerStatement interface {
|
||||
Serializer
|
||||
Statement
|
||||
HasProjections
|
||||
}
|
||||
|
||||
// HasProjections interface
|
||||
type HasProjections interface {
|
||||
projections() ProjectionList
|
||||
}
|
||||
|
||||
// SerializerHasProjections interface is combination of Serializer and HasProjections interface
|
||||
type SerializerHasProjections interface {
|
||||
Serializer
|
||||
HasProjections
|
||||
}
|
||||
|
||||
// serializerStatementInterfaceImpl struct
|
||||
type serializerStatementInterfaceImpl struct {
|
||||
dialect Dialect
|
||||
statementType StatementType
|
||||
parent SerializerStatement
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) Sql() (query string, args []interface{}) {
|
||||
|
||||
queryData := &SQLBuilder{Dialect: s.dialect}
|
||||
|
||||
s.parent.serialize(s.statementType, queryData, NoWrap)
|
||||
|
||||
query, args = queryData.finalize()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) DebugSql() (query string) {
|
||||
sqlBuilder := &SQLBuilder{Dialect: s.dialect, Debug: true}
|
||||
|
||||
s.parent.serialize(s.statementType, sqlBuilder, NoWrap)
|
||||
|
||||
query, _ = sqlBuilder.finalize()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) Query(db qrm.Queryable, destination interface{}) error {
|
||||
return s.QueryContext(context.Background(), db, destination)
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) QueryContext(ctx context.Context, db qrm.Queryable, destination interface{}) error {
|
||||
query, args := s.Sql()
|
||||
|
||||
callLogger(ctx, s)
|
||||
|
||||
var rowsProcessed int64
|
||||
var err error
|
||||
|
||||
duration := duration(func() {
|
||||
rowsProcessed, err = qrm.Query(ctx, db, query, args, destination)
|
||||
})
|
||||
|
||||
callQueryLoggerFunc(ctx, QueryInfo{
|
||||
Statement: s,
|
||||
RowsProcessed: rowsProcessed,
|
||||
Duration: duration,
|
||||
Err: err,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) Exec(db qrm.Executable) (res sql.Result, err error) {
|
||||
return s.ExecContext(context.Background(), db)
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) ExecContext(ctx context.Context, db qrm.Executable) (res sql.Result, err error) {
|
||||
query, args := s.Sql()
|
||||
|
||||
callLogger(ctx, s)
|
||||
|
||||
duration := duration(func() {
|
||||
res, err = db.ExecContext(ctx, query, args...)
|
||||
})
|
||||
|
||||
var rowsAffected int64
|
||||
|
||||
if err == nil {
|
||||
rowsAffected, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
callQueryLoggerFunc(ctx, QueryInfo{
|
||||
Statement: s,
|
||||
RowsProcessed: rowsAffected,
|
||||
Duration: duration,
|
||||
Err: err,
|
||||
})
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *serializerStatementInterfaceImpl) Rows(ctx context.Context, db qrm.Queryable) (*Rows, error) {
|
||||
query, args := s.Sql()
|
||||
|
||||
callLogger(ctx, s)
|
||||
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
|
||||
duration := duration(func() {
|
||||
rows, err = db.QueryContext(ctx, query, args...)
|
||||
})
|
||||
|
||||
callQueryLoggerFunc(ctx, QueryInfo{
|
||||
Statement: s,
|
||||
Duration: duration,
|
||||
Err: err,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scanContext, err := qrm.NewScanContext(rows)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Rows{
|
||||
Rows: rows,
|
||||
scanContext: scanContext,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func duration(f func()) time.Duration {
|
||||
start := time.Now()
|
||||
|
||||
f()
|
||||
|
||||
return time.Since(start)
|
||||
}
|
||||
|
||||
// ExpressionStatement interfacess
|
||||
type ExpressionStatement interface {
|
||||
Expression
|
||||
Statement
|
||||
HasProjections
|
||||
}
|
||||
|
||||
// NewExpressionStatementImpl creates new expression statement
|
||||
func NewExpressionStatementImpl(Dialect Dialect, statementType StatementType, parent ExpressionStatement, clauses ...Clause) ExpressionStatement {
|
||||
return &expressionStatementImpl{
|
||||
ExpressionInterfaceImpl{Parent: parent},
|
||||
statementImpl{
|
||||
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
|
||||
parent: parent,
|
||||
dialect: Dialect,
|
||||
statementType: statementType,
|
||||
},
|
||||
Clauses: clauses,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type expressionStatementImpl struct {
|
||||
ExpressionInterfaceImpl
|
||||
statementImpl
|
||||
}
|
||||
|
||||
func (s *expressionStatementImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||
s.serialize(statement, out)
|
||||
}
|
||||
|
||||
// NewStatementImpl creates new statementImpl
|
||||
func NewStatementImpl(Dialect Dialect, statementType StatementType, parent SerializerStatement, clauses ...Clause) SerializerStatement {
|
||||
return &statementImpl{
|
||||
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
|
||||
parent: parent,
|
||||
dialect: Dialect,
|
||||
statementType: statementType,
|
||||
},
|
||||
Clauses: clauses,
|
||||
}
|
||||
}
|
||||
|
||||
type statementImpl struct {
|
||||
serializerStatementInterfaceImpl
|
||||
|
||||
Clauses []Clause
|
||||
}
|
||||
|
||||
func (s *statementImpl) projections() ProjectionList {
|
||||
for _, clause := range s.Clauses {
|
||||
if selectClause, ok := clause.(ClauseWithProjections); ok {
|
||||
return selectClause.Projections()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *statementImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
if !contains(options, NoWrap) {
|
||||
out.WriteString("(")
|
||||
out.IncreaseIdent()
|
||||
}
|
||||
|
||||
if contains(options, Ident) {
|
||||
out.IncreaseIdent()
|
||||
}
|
||||
|
||||
for _, clause := range s.Clauses {
|
||||
clause.Serialize(s.statementType, out, FallTrough(options)...)
|
||||
}
|
||||
|
||||
if contains(options, Ident) {
|
||||
out.DecreaseIdent()
|
||||
out.NewLine()
|
||||
}
|
||||
|
||||
if !contains(options, NoWrap) {
|
||||
out.DecreaseIdent()
|
||||
out.NewLine()
|
||||
out.WriteString(")")
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package jet
|
||||
|
||||
// StringExpression interface
|
||||
type StringExpression interface {
|
||||
Expression
|
||||
|
||||
EQ(rhs StringExpression) BoolExpression
|
||||
NOT_EQ(rhs StringExpression) BoolExpression
|
||||
IS_DISTINCT_FROM(rhs StringExpression) BoolExpression
|
||||
IS_NOT_DISTINCT_FROM(rhs StringExpression) BoolExpression
|
||||
|
||||
LT(rhs StringExpression) BoolExpression
|
||||
LT_EQ(rhs StringExpression) BoolExpression
|
||||
GT(rhs StringExpression) BoolExpression
|
||||
GT_EQ(rhs StringExpression) BoolExpression
|
||||
BETWEEN(min, max StringExpression) BoolExpression
|
||||
NOT_BETWEEN(min, max StringExpression) BoolExpression
|
||||
|
||||
CONCAT(rhs Expression) StringExpression
|
||||
|
||||
LIKE(pattern StringExpression) BoolExpression
|
||||
NOT_LIKE(pattern StringExpression) BoolExpression
|
||||
|
||||
REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression
|
||||
NOT_REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression
|
||||
}
|
||||
|
||||
type stringInterfaceImpl struct {
|
||||
parent StringExpression
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) EQ(rhs StringExpression) BoolExpression {
|
||||
return Eq(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) NOT_EQ(rhs StringExpression) BoolExpression {
|
||||
return NotEq(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) IS_DISTINCT_FROM(rhs StringExpression) BoolExpression {
|
||||
return IsDistinctFrom(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs StringExpression) BoolExpression {
|
||||
return IsNotDistinctFrom(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) GT(rhs StringExpression) BoolExpression {
|
||||
return Gt(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) GT_EQ(rhs StringExpression) BoolExpression {
|
||||
return GtEq(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) LT(rhs StringExpression) BoolExpression {
|
||||
return Lt(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) LT_EQ(rhs StringExpression) BoolExpression {
|
||||
return LtEq(s.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) BETWEEN(min, max StringExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(s.parent, min, max, false)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) NOT_BETWEEN(min, max StringExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(s.parent, min, max, true)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) CONCAT(rhs Expression) StringExpression {
|
||||
return newBinaryStringOperatorExpression(s.parent, rhs, StringConcatOperator)
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) LIKE(pattern StringExpression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(s.parent, pattern, "LIKE")
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) NOT_LIKE(pattern StringExpression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(s.parent, pattern, "NOT LIKE")
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(s.parent, pattern, StringRegexpLikeOperator, Bool(len(caseSensitive) > 0 && caseSensitive[0]))
|
||||
}
|
||||
|
||||
func (s *stringInterfaceImpl) NOT_REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(s.parent, pattern, StringNotRegexpLikeOperator, Bool(len(caseSensitive) > 0 && caseSensitive[0]))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------//
|
||||
func newBinaryStringOperatorExpression(lhs, rhs Expression, operator string) StringExpression {
|
||||
return StringExp(NewBinaryOperatorExpression(lhs, rhs, operator))
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type stringExpressionWrapper struct {
|
||||
stringInterfaceImpl
|
||||
Expression
|
||||
}
|
||||
|
||||
func newStringExpressionWrap(expression Expression) StringExpression {
|
||||
stringExpressionWrap := stringExpressionWrapper{Expression: expression}
|
||||
stringExpressionWrap.stringInterfaceImpl.parent = &stringExpressionWrap
|
||||
return &stringExpressionWrap
|
||||
}
|
||||
|
||||
// StringExp is string expression wrapper around arbitrary expression.
|
||||
// Allows go compiler to see any expression as string expression.
|
||||
// Does not add sql cast to generated sql builder output.
|
||||
func StringExp(expression Expression) StringExpression {
|
||||
return newStringExpressionWrap(expression)
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package jet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringEQ(t *testing.T) {
|
||||
exp := table3StrCol.EQ(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 = table2.col_str)")
|
||||
exp = table3StrCol.EQ(String("JOHN"))
|
||||
assertClauseSerialize(t, exp, "(table3.col2 = $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringNOT_EQ(t *testing.T) {
|
||||
exp := table3StrCol.NOT_EQ(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 != table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.NOT_EQ(String("JOHN")), "(table3.col2 != $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringExpressionIS_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.IS_DISTINCT_FROM(table2ColStr), "(table3.col2 IS DISTINCT FROM table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.IS_DISTINCT_FROM(String("JOHN")), "(table3.col2 IS DISTINCT FROM $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.IS_NOT_DISTINCT_FROM(table2ColStr), "(table3.col2 IS NOT DISTINCT FROM table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.IS_NOT_DISTINCT_FROM(String("JOHN")), "(table3.col2 IS NOT DISTINCT FROM $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringGT(t *testing.T) {
|
||||
exp := table3StrCol.GT(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 > table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.GT(String("JOHN")), "(table3.col2 > $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringGT_EQ(t *testing.T) {
|
||||
exp := table3StrCol.GT_EQ(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 >= table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.GT_EQ(String("JOHN")), "(table3.col2 >= $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringLT(t *testing.T) {
|
||||
exp := table3StrCol.LT(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 < table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.LT(String("JOHN")), "(table3.col2 < $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringLT_EQ(t *testing.T) {
|
||||
exp := table3StrCol.LT_EQ(table2ColStr)
|
||||
assertClauseSerialize(t, exp, "(table3.col2 <= table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.LT_EQ(String("JOHN")), "(table3.col2 <= $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringCONCAT(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.CONCAT(table2ColStr), "(table3.col2 || table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.CONCAT(String("JOHN")), "(table3.col2 || $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringLIKE(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.LIKE(table2ColStr), "(table3.col2 LIKE table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.LIKE(String("JOHN")), "(table3.col2 LIKE $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringNOT_LIKE(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.NOT_LIKE(table2ColStr), "(table3.col2 NOT LIKE table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.NOT_LIKE(String("JOHN")), "(table3.col2 NOT LIKE $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringREGEXP_LIKE(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "(table3.col2 REGEXP table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), true), "(table3.col2 REGEXP $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringNOT_REGEXP_LIKE(t *testing.T) {
|
||||
assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(table2ColStr), "(table3.col2 NOT REGEXP table2.col_str)")
|
||||
assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 NOT REGEXP $1)", "JOHN")
|
||||
}
|
||||
|
||||
func TestStringExp(t *testing.T) {
|
||||
assertClauseSerialize(t, StringExp(table2ColFloat), "table2.col_float")
|
||||
assertClauseSerialize(t, StringExp(table2ColFloat).NOT_LIKE(String("abc")), "(table2.col_float NOT LIKE $1)", "abc")
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user