├── .github
└── workflows
│ ├── ci.yml
│ └── clean.yml
├── .gitignore
├── .jvmopts
├── CONTRIBUTING.md
├── CONTRIBUTORS.md
├── LICENSE
├── README.md
├── benchmarks
└── src
│ └── main
│ ├── resources
│ └── github.graphql
│ └── scala
│ └── ParserBenchmark.scala
├── build.sbt
├── codecov.yml
├── demo
└── src
│ └── main
│ ├── resources
│ ├── assets
│ │ └── playground.html
│ ├── db
│ │ └── world.sql
│ └── logback.xml
│ └── scala
│ └── demo
│ ├── DemoServer.scala
│ ├── GraphQLService.scala
│ ├── Main.scala
│ ├── starwars
│ └── StarWarsMapping.scala
│ └── world
│ ├── WorldData.scala
│ └── WorldMapping.scala
├── docker-compose.yml
├── docs
├── CONTRIBUTING.md
├── directory.conf
├── index.md
└── tutorial
│ ├── db-backed-model.md
│ ├── directory.conf
│ ├── in-memory-model.md
│ └── intro.md
├── header.md
├── modules
├── circe
│ └── src
│ │ ├── main
│ │ └── scala
│ │ │ └── circemapping.scala
│ │ └── test
│ │ └── scala
│ │ ├── CirceData.scala
│ │ ├── CirceEffectData.scala
│ │ ├── CirceEffectSuite.scala
│ │ ├── CircePrioritySuite.scala
│ │ └── CirceSuite.scala
├── core
│ └── src
│ │ ├── main
│ │ ├── scala-2
│ │ │ └── syntax2.scala
│ │ ├── scala-3
│ │ │ └── syntax3.scala
│ │ └── scala
│ │ │ ├── ast.scala
│ │ │ ├── compiler.scala
│ │ │ ├── composedmapping.scala
│ │ │ ├── cursor.scala
│ │ │ ├── introspection.scala
│ │ │ ├── jsonextractors.scala
│ │ │ ├── mapping.scala
│ │ │ ├── minimizer.scala
│ │ │ ├── operation.scala
│ │ │ ├── parser.scala
│ │ │ ├── predicate.scala
│ │ │ ├── problem.scala
│ │ │ ├── query.scala
│ │ │ ├── queryinterpreter.scala
│ │ │ ├── result.scala
│ │ │ ├── schema.scala
│ │ │ ├── syntax.scala
│ │ │ ├── validationfailure.scala
│ │ │ └── valuemapping.scala
│ │ └── test
│ │ └── scala
│ │ ├── GraphQLResponseTests.scala
│ │ ├── arb
│ │ └── AstArb.scala
│ │ ├── compiler
│ │ ├── AttributesSuite.scala
│ │ ├── CascadeSuite.scala
│ │ ├── CompilerSuite.scala
│ │ ├── DirectivesSuite.scala
│ │ ├── EnvironmentSuite.scala
│ │ ├── FieldMergeSuite.scala
│ │ ├── FragmentSuite.scala
│ │ ├── InputValuesSuite.scala
│ │ ├── PredicatesSuite.scala
│ │ ├── PreserveArgsElaborator.scala
│ │ ├── ProblemSuite.scala
│ │ ├── QuerySizeSuite.scala
│ │ ├── ScalarsSuite.scala
│ │ ├── SkipIncludeSuite.scala
│ │ ├── TestMapping.scala
│ │ └── VariablesSuite.scala
│ │ ├── composed
│ │ ├── ComposedData.scala
│ │ ├── ComposedListSuite.scala
│ │ └── ComposedSuite.scala
│ │ ├── directives
│ │ ├── DirectiveValidationSuite.scala
│ │ ├── QueryDirectivesSuite.scala
│ │ └── SchemaDirectivesSuite.scala
│ │ ├── effects
│ │ ├── ValueEffectData.scala
│ │ └── ValueEffectSuite.scala
│ │ ├── extensions
│ │ └── ExtensionsSuite.scala
│ │ ├── introspection
│ │ └── IntrospectionSuite.scala
│ │ ├── laws
│ │ └── ResultSuite.scala
│ │ ├── mapping
│ │ └── MappingValidatorSuite.scala
│ │ ├── minimizer
│ │ └── MinimizerSuite.scala
│ │ ├── parser
│ │ └── ParserSuite.scala
│ │ ├── schema
│ │ └── SchemaSuite.scala
│ │ ├── sdl
│ │ └── SDLSuite.scala
│ │ ├── starwars
│ │ ├── StarWarsData.scala
│ │ └── StarWarsSuite.scala
│ │ └── subscription
│ │ └── SubscriptionSuite.scala
├── docs
│ └── src
│ │ └── main
│ │ └── scala
│ │ └── grackle
│ │ └── Output.scala
├── doobie-core
│ └── src
│ │ ├── main
│ │ └── scala
│ │ │ ├── DoobieMapping.scala
│ │ │ ├── DoobieMappingCompanion.scala
│ │ │ ├── DoobieMonitor.scala
│ │ │ └── package.scala
│ │ └── test
│ │ └── scala
│ │ └── DoobieDatabaseSuite.scala
├── doobie-mssql
│ └── src
│ │ ├── main
│ │ └── scala
│ │ │ └── DoobieMSSqlMapping.scala
│ │ └── test
│ │ ├── resources
│ │ └── scripts
│ │ │ ├── entrypoint.sh
│ │ │ └── init.sql
│ │ └── scala
│ │ ├── DoobieMSSqlDatabaseSuite.scala
│ │ └── DoobieMSSqlSuites.scala
├── doobie-oracle
│ └── src
│ │ ├── main
│ │ └── scala
│ │ │ └── DoobieOracleMapping.scala
│ │ └── test
│ │ ├── resources
│ │ └── scripts
│ │ │ ├── 01_create_user.sql
│ │ │ └── 02_create_tables.sh
│ │ └── scala
│ │ ├── DoobieOracleDatabaseSuite.scala
│ │ └── DoobieOracleSuites.scala
├── doobie-pg
│ └── src
│ │ ├── main
│ │ └── scala
│ │ │ └── DoobiePgMapping.scala
│ │ └── test
│ │ └── scala
│ │ ├── DoobiePgDatabaseSuite.scala
│ │ └── DoobiePgSuites.scala
├── generic
│ └── src
│ │ ├── main
│ │ ├── scala-2
│ │ │ └── genericmapping2.scala
│ │ ├── scala-3
│ │ │ └── genericmapping3.scala
│ │ └── scala
│ │ │ ├── CursorBuilder.scala
│ │ │ └── genericmapping.scala
│ │ └── test
│ │ └── scala
│ │ ├── DerivationSuite.scala
│ │ ├── EffectsSuite.scala
│ │ ├── RecursionSuite.scala
│ │ └── ScalarsSuite.scala
├── skunk
│ ├── js-jvm
│ │ └── src
│ │ │ └── test
│ │ │ └── scala
│ │ │ ├── SkunkDatabaseSuite.scala
│ │ │ ├── SkunkSuites.scala
│ │ │ └── subscription
│ │ │ ├── SubscriptionMapping.scala
│ │ │ └── SubscriptionSuite.scala
│ └── shared
│ │ └── src
│ │ └── main
│ │ └── scala
│ │ ├── SkunkMapping.scala
│ │ ├── SkunkMappingCompanion.scala
│ │ ├── SkunkMonitor.scala
│ │ └── package.scala
├── sql-core
│ └── src
│ │ ├── main
│ │ ├── scala-2
│ │ │ └── Like.scala
│ │ ├── scala-3
│ │ │ └── Like.scala
│ │ └── scala
│ │ │ ├── FailedJoin.scala
│ │ │ ├── SqlMapping.scala
│ │ │ ├── SqlModule.scala
│ │ │ ├── SqlMonitor.scala
│ │ │ └── SqlStatsMonitor.scala
│ │ └── test
│ │ ├── resources
│ │ └── logback-test.xml
│ │ └── scala
│ │ ├── SqlArrayJoinMapping.scala
│ │ ├── SqlArrayJoinSuite.scala
│ │ ├── SqlCoalesceMapping.scala
│ │ ├── SqlCoalesceSuite.scala
│ │ ├── SqlComposedWorldMapping.scala
│ │ ├── SqlComposedWorldSuite.scala
│ │ ├── SqlCompositeKeyMapping.scala
│ │ ├── SqlCompositeKeySuite.scala
│ │ ├── SqlCursorJsonMapping.scala
│ │ ├── SqlCursorJsonSuite.scala
│ │ ├── SqlEmbedding2Mapping.scala
│ │ ├── SqlEmbedding2Suite.scala
│ │ ├── SqlEmbedding3Mapping.scala
│ │ ├── SqlEmbedding3Suite.scala
│ │ ├── SqlEmbeddingMapping.scala
│ │ ├── SqlEmbeddingSuite.scala
│ │ ├── SqlFilterJoinAliasMapping.scala
│ │ ├── SqlFilterJoinAliasSuite.scala
│ │ ├── SqlFilterOrderOffsetLimit2Mapping.scala
│ │ ├── SqlFilterOrderOffsetLimit2Suite.scala
│ │ ├── SqlFilterOrderOffsetLimitMapping.scala
│ │ ├── SqlFilterOrderOffsetLimitSuite.scala
│ │ ├── SqlGraphMapping.scala
│ │ ├── SqlGraphSuite.scala
│ │ ├── SqlInterfacesMapping.scala
│ │ ├── SqlInterfacesMapping2.scala
│ │ ├── SqlInterfacesSuite.scala
│ │ ├── SqlInterfacesSuite2.scala
│ │ ├── SqlJsonbMapping.scala
│ │ ├── SqlJsonbSuite.scala
│ │ ├── SqlLikeMapping.scala
│ │ ├── SqlLikeSuite.scala
│ │ ├── SqlMappingValidatorInvalidMapping.scala
│ │ ├── SqlMappingValidatorInvalidSuite.scala
│ │ ├── SqlMappingValidatorValidMapping.scala
│ │ ├── SqlMappingValidatorValidSuite.scala
│ │ ├── SqlMixedMapping.scala
│ │ ├── SqlMixedSuite.scala
│ │ ├── SqlMovieMapping.scala
│ │ ├── SqlMovieSuite.scala
│ │ ├── SqlMutationMapping.scala
│ │ ├── SqlMutationSuite.scala
│ │ ├── SqlNestedEffectsMapping.scala
│ │ ├── SqlNestedEffectsSuite.scala
│ │ ├── SqlPaging1Mapping.scala
│ │ ├── SqlPaging1Suite.scala
│ │ ├── SqlPaging2Mapping.scala
│ │ ├── SqlPaging2Suite.scala
│ │ ├── SqlPaging3Mapping.scala
│ │ ├── SqlPaging3Suite.scala
│ │ ├── SqlProjectionMapping.scala
│ │ ├── SqlProjectionSuite.scala
│ │ ├── SqlRecursiveInterfacesMapping.scala
│ │ ├── SqlRecursiveInterfacesSuite.scala
│ │ ├── SqlSiblingListsMapping.scala
│ │ ├── SqlSiblingListsSuite.scala
│ │ ├── SqlTestMapping.scala
│ │ ├── SqlTreeMapping.scala
│ │ ├── SqlTreeSuite.scala
│ │ ├── SqlUnionSuite.scala
│ │ ├── SqlUnionsMapping.scala
│ │ ├── SqlWorldCompilerSuite.scala
│ │ ├── SqlWorldMapping.scala
│ │ └── SqlWorldSuite.scala
└── sql-pg
│ ├── js-jvm
│ └── src
│ │ └── test
│ │ └── scala
│ │ └── SqlPgDatabaseSuite.scala
│ └── shared
│ └── src
│ └── main
│ └── scala
│ └── SqlPgMapping.scala
├── profile
└── src
│ └── main
│ └── scala
│ └── Bench.scala
├── project
├── build.properties
├── plugins.sbt
└── project
│ └── plugins.sbt
├── run-local.sh
├── scripts
├── mssql-query.sh
├── oracle-opts.sql
├── oracle-query.sh
└── pg-query.sh
├── stop-local.sh
└── testdata
├── mssql
├── array-join.sql
├── coalesce.sql
├── composite-keys.sql
├── cursor-json.sql
├── embedding.sql
├── embedding2.sql
├── filter-join-alias.sql
├── filter-order-offset-limit-2.sql
├── filter-order-offset-limit.sql
├── graph.sql
├── interfaces.sql
├── jsonb.sql
├── like.sql
├── movies.sql
├── mutation.sql
├── projection.sql
├── recursive-interfaces.sql
├── sibling-lists.sql
├── tree.sql
├── unions.sql
└── world.sql
├── oracle
├── array-join.sql
├── coalesce.sql
├── composite-keys.sql
├── cursor-json.sql
├── embedding.sql
├── embedding2.sql
├── filter-join-alias.sql
├── filter-order-offset-limit-2.sql
├── filter-order-offset-limit.sql
├── graph.sql
├── interfaces.sql
├── jsonb.sql
├── like.sql
├── movies.sql
├── mutation.sql
├── projection.sql
├── recursive-interfaces.sql
├── sibling-lists.sql
├── tree.sql
├── unions.sql
└── world.sql
└── pg
├── array-join.sql
├── coalesce.sql
├── composite-keys.sql
├── cursor-json.sql
├── embedding.sql
├── embedding2.sql
├── filter-join-alias.sql
├── filter-order-offset-limit-2.sql
├── filter-order-offset-limit.sql
├── graph.sql
├── interfaces.sql
├── jsonb.sql
├── like.sql
├── movies.sql
├── mutation.sql
├── projection.sql
├── recursive-interfaces.sql
├── sibling-lists.sql
├── tree.sql
├── unions.sql
└── world.sql
/.github/workflows/clean.yml:
--------------------------------------------------------------------------------
1 | # This file was automatically generated by sbt-github-actions using the
2 | # githubWorkflowGenerate task. You should add and commit this file to
3 | # your git repository. It goes without saying that you shouldn't edit
4 | # this file by hand! Instead, if you wish to make changes, you should
5 | # change your sbt build configuration to revise the workflow description
6 | # to meet your needs, then regenerate this file.
7 |
8 | name: Clean
9 |
10 | on: push
11 |
12 | jobs:
13 | delete-artifacts:
14 | name: Delete Artifacts
15 | runs-on: ubuntu-latest
16 | env:
17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18 | steps:
19 | - name: Delete artifacts
20 | run: |
21 | # Customize those three lines with your repository and credentials:
22 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }}
23 |
24 | # A shortcut to call GitHub API.
25 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; }
26 |
27 | # A temporary file which receives HTTP response headers.
28 | TMPFILE=/tmp/tmp.$$
29 |
30 | # An associative array, key: artifact name, value: number of artifacts of that name.
31 | declare -A ARTCOUNT
32 |
33 | # Process all artifacts on this repository, loop on returned "pages".
34 | URL=$REPO/actions/artifacts
35 | while [[ -n "$URL" ]]; do
36 |
37 | # Get current page, get response headers in a temporary file.
38 | JSON=$(ghapi --dump-header $TMPFILE "$URL")
39 |
40 | # Get URL of next page. Will be empty if we are at the last page.
41 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*/' -e 's/>.*//')
42 | rm -f $TMPFILE
43 |
44 | # Number of artifacts on this page:
45 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') ))
46 |
47 | # Loop on all artifacts on this page.
48 | for ((i=0; $i < $COUNT; i++)); do
49 |
50 | # Get name of artifact and count instances of this name.
51 | name=$(jq <<<$JSON -r ".artifacts[$i].name?")
52 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1))
53 |
54 | id=$(jq <<<$JSON -r ".artifacts[$i].id?")
55 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") ))
56 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size
57 | ghapi -X DELETE $REPO/actions/artifacts/$id
58 | done
59 | done
60 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.log
3 |
4 | # these are moved into the doc project by the build
5 | modules/docs/src/main/tut/changelog.md
6 | modules/docs/src/main/tut/license.md
7 |
8 | # sbt specific
9 | dist/*
10 | target/
11 | lib_managed/
12 | src_managed/
13 | project/boot/
14 | project/plugins/project/
15 | project/hydra.sbt
16 | local.sbt
17 |
18 | # Scala-IDE specific
19 | .scala_dependencies
20 | .cache
21 | .classpath
22 | .project
23 | .worksheet/
24 | bin/
25 | .settings/
26 |
27 | # OS X
28 | .DS_Store
29 |
30 | # Ctags
31 | .tags
32 |
33 | # ENSIME
34 | .ensime
35 | .ensime_cache/
36 |
37 | # IntelliJ
38 | .idea/
39 | *.ipr
40 | *.iws
41 | *.iml
42 |
43 | # Mill
44 | out/
45 |
46 | # VSCode
47 | .vscode/
48 |
49 | # Bloop/Metals
50 | .bloop/
51 | .metals/
52 | metals.sbt
53 | .bsp
54 |
55 | # Vim
56 | *.swp
57 |
58 | # Worksheets
59 | *.worksheet.sc
60 |
61 | # Java Flight Recorder
62 | *.jfr
63 |
64 | # Misc
65 | query*.sql
66 |
--------------------------------------------------------------------------------
/.jvmopts:
--------------------------------------------------------------------------------
1 | -Dsbt.color=always
2 | -Dsbt.supershell=true
3 | -Xms2g
4 | -Xmx4g
5 | -Xss4m
6 | -XX:+UseG1GC
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | docs/CONTRIBUTING.md
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | # Contributors
2 |
3 | This project is the work of numerous individuals. Names are listed below, ordered alphabetically by given name, along
4 | with their GitHub handle and a link to their profile.
5 |
6 | Each individual maintains their own description, and if you don't see your name listed here, please do open a PR!
7 |
8 | Also see the [GitHub contributor stats](https://github.com/typelevel/grackle/graphs/contributors).
9 |
10 | - Alexandre Bertails ([@betehess](https://github.com/betehess))
11 | - Arman Bilge ([@armanbilge](https://github.com/armanbilge))
12 | - Carlos Quiroz ([@cquiroz](https://github.com/cquiroz))
13 | - Daniel Tattan-Birch ([@dantb](https://github.com/dantb))
14 | - Jack Wheatley ([@jbwheatley](https://github.com/jbwheatley))
15 | - Jorge Botto ([@jf-botto](https://github.com/jf-botto))
16 | - Keir Lawson ([@keirlawson](https://github.com/keirlawson))
17 | - Luke Matthews ([@QuitHub](https://github.com/QuitHub))
18 | - Magnus Valle ([@mvalle](https://github.com/mvalle))
19 | - Maya Driver ([@yasuba](https://github.com/yasuba))
20 | - Miles Sabin ([@milessabin](https://github.com/milessabin))
21 | - Rafal Piotrowski ([@rpiotrow](https://github.com/rpiotrow))
22 | - Raúl Piaggio ([@rpiaggio](https://github.com/rpiaggio))
23 | - Rob Norris ([@tpolecat](https://github.com/tpolecat))
24 | - Shane Walker ([@swalker2m](https://github.com/swalker2m))
25 | - Tim Spence ([@TimWSpence](https://github.com/TimWSpence))
26 |
--------------------------------------------------------------------------------
/benchmarks/src/main/scala/ParserBenchmark.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.benchmarks
17 |
18 | import grackle.Schema
19 | import org.openjdk.jmh.annotations._
20 | import org.openjdk.jmh.infra.Blackhole;
21 |
22 | import java.util.concurrent.TimeUnit
23 | import scala.io.Source
24 |
25 | /**
26 | * To do comparative benchmarks between versions:
27 | *
28 | * benchmarks/run-benchmark ParserBenchmark
29 | *
30 | * This will generate results in `benchmarks/results`.
31 | *
32 | * Or to run the benchmark from within sbt:
33 | *
34 | * jmh:run -i 10 -wi 10 -f 2 -t 1 grackle.benchmarks.ParserBenchmark
35 | *
36 | * Which means "10 iterations", "10 warm-up iterations", "2 forks", "1 thread".
37 | * Please note that benchmarks should be usually executed at least in
38 | * 10 iterations (as a rule of thumb), but more is better.
39 | */
40 | @State(Scope.Thread)
41 | @BenchmarkMode(Array(Mode.Throughput))
42 | @OutputTimeUnit(TimeUnit.SECONDS)
43 | class ParserBenchmark {
44 |
45 | @Param(Array("100"))
46 | var size: Int = _
47 |
48 | val schema = Source.fromResource("github.graphql").mkString
49 |
50 | @Benchmark
51 | def parseSchema(blackhole: Blackhole) = {
52 | for (_ <- 0 to size) {
53 | val parsed = Schema(schema)
54 | blackhole.consume(parsed)
55 | }
56 | }
57 |
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | project:
4 | default:
5 | target: auto
6 | threshold: 1%
7 |
--------------------------------------------------------------------------------
/demo/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %highlight(%-5level) - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/demo/src/main/scala/demo/DemoServer.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package demo
17 |
18 | import cats.effect.IO
19 | import cats.effect.Resource
20 | import cats.syntax.all._
21 | import com.comcast.ip4s._
22 | import org.http4s.{HttpApp, HttpRoutes}
23 | import org.http4s.ember.server.EmberServerBuilder
24 | import org.http4s.server.middleware.{ErrorAction, ErrorHandling, Logger}
25 | import org.http4s.server.staticcontent.resourceServiceBuilder
26 |
27 | // #server
28 | object DemoServer {
29 | def mkServer(graphQLRoutes: HttpRoutes[IO]): Resource[IO, Unit] = {
30 | val httpApp0 = (
31 | // Routes for static resources, i.e. GraphQL Playground
32 | resourceServiceBuilder[IO]("/assets").toRoutes <+>
33 | // GraphQL routes
34 | graphQLRoutes
35 | ).orNotFound
36 |
37 | val httpApp = Logger.httpApp(true, false)(httpApp0)
38 |
39 | val withErrorLogging: HttpApp[IO] = ErrorHandling.Recover.total(
40 | ErrorAction.log(
41 | httpApp,
42 | messageFailureLogAction = errorHandler,
43 | serviceErrorLogAction = errorHandler))
44 |
45 | // Spin up the server ...
46 | EmberServerBuilder.default[IO]
47 | .withHost(ip"0.0.0.0")
48 | .withPort(port"8080")
49 | .withHttpApp(withErrorLogging)
50 | .build.void
51 | }
52 |
53 | def errorHandler(t: Throwable, msg: => String) : IO[Unit] =
54 | IO.println(msg) >> IO.println(t) >> IO.println(t.printStackTrace())
55 | }
56 | // #server
57 |
--------------------------------------------------------------------------------
/demo/src/main/scala/demo/GraphQLService.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package demo
17 |
18 | import cats.effect.Concurrent
19 | import cats.syntax.all._
20 | import grackle.Mapping
21 | import io.circe.{Json, ParsingFailure, parser}
22 | import org.http4s.circe._
23 | import org.http4s.dsl.Http4sDsl
24 | import org.http4s.{HttpRoutes, InvalidMessageBodyFailure, ParseFailure, QueryParamDecoder}
25 |
26 | // #service
27 | object GraphQLService {
28 | def mkRoutes[F[_]: Concurrent](prefix: String)(mapping: Mapping[F]): HttpRoutes[F] = {
29 | val dsl = new Http4sDsl[F]{}
30 | import dsl._
31 |
32 | implicit val jsonQPDecoder: QueryParamDecoder[Json] =
33 | QueryParamDecoder[String].emap { s =>
34 | parser.parse(s).leftMap {
35 | case ParsingFailure(msg, _) => ParseFailure("Invalid variables", msg)
36 | }
37 | }
38 |
39 | object QueryMatcher
40 | extends QueryParamDecoderMatcher[String]("query")
41 | object OperationNameMatcher
42 | extends OptionalQueryParamDecoderMatcher[String]("operationName")
43 | object VariablesMatcher
44 | extends OptionalValidatingQueryParamDecoderMatcher[Json]("variables")
45 |
46 | HttpRoutes.of[F] {
47 | // GraphQL query is embedded in the URI query string when queried via GET
48 | case GET -> Root / `prefix` :?
49 | QueryMatcher(query) +& OperationNameMatcher(op) +& VariablesMatcher(vars0) =>
50 | vars0.sequence.fold(
51 | errors => BadRequest(errors.map(_.sanitized).mkString_("", ",", "")),
52 | vars =>
53 | for {
54 | result <- mapping.compileAndRun(query, op, vars)
55 | resp <- Ok(result)
56 | } yield resp
57 | )
58 |
59 | // GraphQL query is embedded in a Json request body when queried via POST
60 | case req @ POST -> Root / `prefix` =>
61 | for {
62 | body <- req.as[Json]
63 | obj <- body.asObject.liftTo[F](
64 | InvalidMessageBodyFailure("Invalid GraphQL query")
65 | )
66 | query <- obj("query").flatMap(_.asString).liftTo[F](
67 | InvalidMessageBodyFailure("Missing query field")
68 | )
69 | op = obj("operationName").flatMap(_.asString)
70 | vars = obj("variables")
71 | result <- mapping.compileAndRun(query, op, vars)
72 | resp <- Ok(result)
73 | } yield resp
74 | }
75 | }
76 | }
77 | // #service
78 |
--------------------------------------------------------------------------------
/demo/src/main/scala/demo/Main.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package demo
17 |
18 | import cats.effect.{ExitCode, IO, IOApp}
19 | import cats.syntax.all._
20 | import demo.starwars.StarWarsMapping
21 | import demo.world.WorldMapping
22 |
23 | import GraphQLService.mkRoutes
24 | import DemoServer.mkServer
25 |
26 | // #main
27 | object Main extends IOApp {
28 | def run(args: List[String]): IO[ExitCode] = {
29 | (for {
30 | starWarsRoutes <- StarWarsMapping[IO].map(mkRoutes("starwars"))
31 | worldRoutes <- WorldMapping[IO].map(mkRoutes("world"))
32 | _ <- mkServer(starWarsRoutes <+> worldRoutes)
33 | } yield ()).useForever
34 | }
35 | }
36 | // #main
37 |
--------------------------------------------------------------------------------
/demo/src/main/scala/demo/world/WorldData.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package demo.world
17 |
18 | import java.util.concurrent.Executors
19 |
20 | import scala.concurrent.ExecutionContext
21 |
22 | import cats.effect.{Async, Resource}
23 | import doobie.hikari.HikariTransactor
24 |
25 | object WorldData {
26 | def mkTransactor[F[_]: Async](connInfo: PostgresConnectionInfo): Resource[F, HikariTransactor[F]] = {
27 | import connInfo._
28 | HikariTransactor.newHikariTransactor[F](
29 | driverClassName,
30 | jdbcUrl,
31 | username,
32 | password,
33 | ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(4))
34 | )
35 | }
36 |
37 | case class PostgresConnectionInfo(host: String, port: Int) {
38 | val driverClassName = "org.postgresql.Driver"
39 | val databaseName = "test"
40 | val jdbcUrl = s"jdbc:postgresql://$host:$port/$databaseName"
41 | val username = "test"
42 | val password = "test"
43 | }
44 |
45 | object PostgresConnectionInfo {
46 | val DefaultPort = 5432
47 | }
48 |
49 | def bindPath(path: String): String =
50 | buildinfo.BuildInfo.baseDirectory + "/" + path
51 | }
52 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | postgres:
3 | image: postgres:11.8
4 | ports:
5 | - "5432:5432"
6 | environment:
7 | - POSTGRES_DB=test
8 | - POSTGRES_USER=test
9 | - POSTGRES_PASSWORD=test
10 | volumes:
11 | - ./testdata/pg/:/docker-entrypoint-initdb.d/
12 | healthcheck:
13 | test: ["CMD-SHELL", "pg_isready -U postgres"]
14 | interval: 5s
15 | timeout: 5s
16 | retries: 5
17 |
18 | oracle:
19 | image: gvenzl/oracle-free:23-slim-faststart
20 | ports:
21 | - "1521:1521"
22 | environment:
23 | ORACLE_PASSWORD: test
24 | volumes:
25 | - ./testdata/oracle/:/grackle-initdb.d/
26 | - ./modules/doobie-oracle/src/test/resources/scripts/:/container-entrypoint-initdb.d/
27 | healthcheck:
28 | test: bash -c "[ -f /tmp/healthy ]"
29 | interval: 10s
30 | timeout: 5s
31 | retries: 10
32 | start_period: 5s
33 | start_interval: 5s
34 |
35 | mssql:
36 | image: mcr.microsoft.com/mssql/server:2022-latest
37 | ports:
38 | - "1433:1433"
39 | environment:
40 | SA_PASSWORD: Test_123_Test
41 | MSSQL_PID: Developer
42 | ACCEPT_EULA: Y
43 | MSSQL_TCP_PORT: 1433
44 | volumes:
45 | - ./testdata/mssql/:/grackle-initdb.d/
46 | - ./modules/doobie-mssql/src/test/resources/scripts/:/container-entrypoint-initdb.d/
47 | entrypoint: ["/bin/bash", "/container-entrypoint-initdb.d/entrypoint.sh"]
48 | healthcheck:
49 | test: bash -c "[ -f /tmp/healthy ]"
50 | interval: 10s
51 | timeout: 5s
52 | retries: 10
53 | start_period: 5s
54 | start_interval: 5s
55 |
--------------------------------------------------------------------------------
/docs/directory.conf:
--------------------------------------------------------------------------------
1 | laika.navigationOrder = [
2 | index.md
3 | CONTRIBUTING.md
4 | ]
5 |
--------------------------------------------------------------------------------
/docs/tutorial/directory.conf:
--------------------------------------------------------------------------------
1 | laika.title = Tutorial
2 | laika.navigationOrder = [
3 | intro.md
4 | in-memory-model.md
5 | db-backed-model.md
6 | ]
7 |
--------------------------------------------------------------------------------
/docs/tutorial/intro.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | This section contains instructions and examples to get you started. It's written in tutorial style, intended to be read start to finish.
4 |
5 | * [In-memory model](in-memory-model.md)
6 | * [DB Backed model](in-memory-model.md)
7 |
--------------------------------------------------------------------------------
/header.md:
--------------------------------------------------------------------------------
1 | # Grackle - GraphQL for the Typelevel stack
2 |
3 | [](https://github.com/typelevel/grackle/actions?query=branch%3Amain+workflow%3A%22Continuous+Integration%22)
4 | [](https://img.shields.io/maven-central/v/org.typelevel/grackle-core_2.13?versionPrefix=0)
5 | [](https://javadoc.io/doc/org.typelevel/grackle-core_2.13)
6 | [](https://typelevel.org/projects/#grackle)
7 | [](https://codecov.io/gh/typelevel/grackle)
8 | [][grackle-dev]
9 |
--------------------------------------------------------------------------------
/modules/circe/src/test/scala/CirceData.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package circetests
18 |
19 | import cats.effect.IO
20 | import cats.data.OptionT
21 | import io.circe.Json
22 | import io.circe.literal._
23 |
24 | import grackle.circe.CirceMapping
25 | import grackle.syntax._
26 |
27 | import Query._
28 | import QueryCompiler._
29 |
30 | object TestCirceMapping extends CirceMapping[IO] {
31 | val schema =
32 | schema"""
33 | type Query {
34 | root: Root
35 | }
36 | type Root {
37 | bool: Boolean
38 | int: Int
39 | float: Float
40 | string: String
41 | id: ID
42 | choice: Choice
43 | array: [Int!]
44 | object: A
45 | numChildren: Int
46 | bigDecimal: BigDecimal
47 | children: [Child!]!
48 | computed: Int
49 | }
50 | enum Choice {
51 | ONE
52 | TWO
53 | THREE
54 | }
55 | interface Child {
56 | id: ID
57 | }
58 | type A implements Child {
59 | id: ID
60 | aField: Int
61 | }
62 | type B implements Child {
63 | id: ID
64 | bField: String
65 | }
66 | scalar BigDecimal
67 | """
68 |
69 | val QueryType = schema.ref("Query")
70 | val RootType = schema.ref("Root")
71 | val BigDecimalType = schema.ref("BigDecimal")
72 |
73 | val data =
74 | json"""
75 | {
76 | "bool": true,
77 | "int": 23,
78 | "float": 1.3,
79 | "string": "foo",
80 | "bigDecimal": 1.2,
81 | "id": "bar",
82 | "array": [1, 2, 3],
83 | "choice": "ONE",
84 | "object": {
85 | "id": "obj",
86 | "aField": 27
87 | },
88 | "children": [
89 | {
90 | "id": "a",
91 | "aField": 11
92 | },
93 | {
94 | "id": "b",
95 | "bField": "quux"
96 | }
97 | ],
98 | "hidden": 13
99 | }
100 | """
101 |
102 | val typeMappings =
103 | List(
104 | ObjectMapping(
105 | tpe = QueryType,
106 | fieldMappings =
107 | List(
108 | CirceField("root", data),
109 | )
110 | ),
111 | ObjectMapping(
112 | tpe = RootType,
113 | fieldMappings =
114 | List(
115 | CursorField("computed", computeField, List("hidden"))
116 | )
117 | ),
118 | LeafMapping[BigDecimal](BigDecimalType)
119 | )
120 |
121 | def computeField(c: Cursor): Result[Option[Int]] = {
122 | (for {
123 | n <- OptionT(c.fieldAs[Json]("hidden").map(_.asNumber))
124 | i <- OptionT(Result(n.toInt))
125 | } yield i+1).value
126 | }
127 |
128 | override val selectElaborator = SelectElaborator {
129 | case (RootType, "numChildren", Nil) =>
130 | Elab.transformChild(_ => Count(Select("children")))
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/modules/circe/src/test/scala/CirceEffectData.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package circetests
18 |
19 | import cats.effect.Sync
20 | import cats.implicits._
21 | import fs2.concurrent.SignallingRef
22 | import io.circe.{Encoder, Json}
23 |
24 | import grackle.circe.CirceMapping
25 | import grackle.syntax._
26 |
27 | class TestCirceEffectMapping[F[_]: Sync](ref: SignallingRef[F, Int]) extends CirceMapping[F] {
28 | val schema =
29 | schema"""
30 | type Query {
31 | foo: Struct!
32 | bar: Struct!
33 | baz: Struct!
34 | qux: Struct!
35 | }
36 | type Struct {
37 | n: Int!
38 | s: String!
39 | }
40 | """
41 |
42 | val QueryType = schema.ref("Query")
43 | val StructType = schema.ref("Struct")
44 |
45 | case class Struct(n: Int, s: String)
46 | implicit val EncodeStruct: Encoder[Struct] = s =>
47 | Json.obj(
48 | "n" -> Json.fromInt(s.n),
49 | "s" -> Json.fromString(s.s)
50 | )
51 |
52 | val typeMappings = List(
53 | ObjectMapping(
54 | tpe = QueryType,
55 | fieldMappings =
56 | List(
57 |
58 | // Compute a CirceCursor
59 | RootEffect.computeCursor("foo")((p, e) =>
60 | ref.update(_+1).as(
61 | Result(circeCursor(p, e,
62 | Json.obj(
63 | "n" -> Json.fromInt(42),
64 | "s" -> Json.fromString("hi")
65 | )
66 | ))
67 | )
68 | ),
69 |
70 | // Compute a Json, let the implementation handle the cursor
71 | RootEffect.computeJson("bar")((_, _) =>
72 | ref.update(_+1).as(
73 | Result(Json.obj(
74 | "n" -> Json.fromInt(42),
75 | "s" -> Json.fromString("ho")
76 | ))
77 | )
78 | ),
79 |
80 | // Compute an encodable value, let the implementation handle json and the cursor
81 | RootEffect.computeEncodable("baz")((_, _) =>
82 | ref.update(_+1).as(
83 | Result(Struct(44, "hee"))
84 | )
85 | ),
86 |
87 | // Compute a CirceCursor focussed on the root
88 | RootEffect.computeCursor("qux")((p, e) =>
89 | ref.update(_+1).as(
90 | Result(circeCursor(Path.from(p.rootTpe), e,
91 | Json.obj(
92 | "qux" ->
93 | Json.obj(
94 | "n" -> Json.fromInt(42),
95 | "s" -> Json.fromString("hi")
96 | )
97 | )
98 | ))
99 | )
100 | )
101 | )
102 | )
103 | )
104 | }
105 |
--------------------------------------------------------------------------------
/modules/circe/src/test/scala/CircePrioritySuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package circetests
18 |
19 | import cats.effect.IO
20 | import io.circe.Json
21 | import io.circe.literal._
22 | import grackle.circe.CirceMapping
23 | import grackle.syntax._
24 | import munit.CatsEffectSuite
25 |
26 | object CircePriorityMapping extends CirceMapping[IO] {
27 |
28 | val schema = schema"""
29 | scalar Foo
30 | type Monkey {
31 | name: Foo
32 | }
33 | type Barrel {
34 | monkey: Monkey
35 | }
36 | type Query {
37 | present: Barrel
38 | fallback: Barrel
39 | }
40 | """
41 |
42 | val QueryType = schema.ref("Query")
43 | val MonkeyType = schema.ref("Monkey")
44 | val BarrelType = schema.ref("Barrel")
45 | val FooType = schema.ref("Foo")
46 |
47 | val typeMappings =
48 | List(
49 | ObjectMapping(
50 | tpe = QueryType,
51 | fieldMappings =
52 | List(
53 | CirceField("present", json"""{ "monkey": { "name": "Bob" }}"""),
54 | CirceField("fallback", json"""{ "monkey": {}}"""),
55 | )
56 | ),
57 | ObjectMapping(
58 | tpe = MonkeyType,
59 | fieldMappings =
60 | List(
61 | CirceField("name", Json.fromString("Steve"))
62 | )
63 | ),
64 | LeafMapping[String](FooType)
65 | )
66 |
67 | }
68 |
69 | final class CircePrioritySuite extends CatsEffectSuite {
70 |
71 | test("Opaque field should not see explicit mapping.") {
72 |
73 | val query = """
74 | query {
75 | present {
76 | monkey {
77 | name
78 | }
79 | }
80 | fallback {
81 | monkey {
82 | name
83 | }
84 | }
85 | }
86 | """
87 |
88 | val expected = json"""
89 | {
90 | "data" : {
91 | "present" : {
92 | "monkey" : {
93 | "name" : "Bob"
94 | }
95 | },
96 | "fallback" : {
97 | "monkey" : {
98 | "name" : "Steve"
99 | }
100 | }
101 | }
102 | }
103 | """
104 |
105 | assertIO(CircePriorityMapping.compileAndRun(query), expected)
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/modules/core/src/main/scala-2/syntax2.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 |
18 | import cats.data.NonEmptyChain
19 | import cats.syntax.all._
20 | import org.typelevel.literally.Literally
21 | import grackle.Ast.Document
22 | import grackle.Schema
23 |
24 | trait VersionSpecificSyntax {
25 | implicit def toStringContextOps(sc: StringContext): StringContextOps =
26 | new StringContextOps(sc)
27 | }
28 |
29 | class StringContextOps(val sc: StringContext) extends AnyVal {
30 | def schema(args: Any*): Schema = macro SchemaLiteral.make
31 | def doc(args: Any*): Document = macro DocumentLiteral.make
32 | }
33 |
34 | private object SchemaLiteral extends Literally[Schema] {
35 | def validate(c: Context)(s: String): Either[String,c.Expr[Schema]] = {
36 | import c.universe._
37 | def mkError(err: Either[Throwable, NonEmptyChain[Problem]]) =
38 | err.fold(
39 | t => s"Internal error: ${t.getMessage}",
40 | ps => s"Invalid schema: ${ps.toList.distinct.mkString("\n 🐞 ", "\n 🐞 ", "\n")}",
41 | )
42 | Schema(s, CompiletimeParsers.schemaParser).toEither.bimap(mkError, _ => c.Expr(q"_root_.grackle.Schema($s, _root_.grackle.CompiletimeParsers.schemaParser).toOption.get"))
43 | }
44 | def make(c: Context)(args: c.Expr[Any]*): c.Expr[Schema] = apply(c)(args: _*)
45 | }
46 |
47 | private object DocumentLiteral extends Literally[Document] {
48 | def validate(c: Context)(s: String): Either[String,c.Expr[Document]] = {
49 | import c.universe._
50 | CompiletimeParsers.parser.parseText(s).toEither.bimap(
51 | _.fold(thr => show"Invalid document: ${thr.getMessage}", _.toList.mkString("\n 🐞 ", "\n 🐞 ", "\n")),
52 | _ => c.Expr(q"_root_.grackle.CompiletimeParsers.parser.parseText($s).toOption.get"),
53 | )
54 | }
55 | def make(c: Context)(args: c.Expr[Any]*): c.Expr[Document] = apply(c)(args: _*)
56 | }
57 |
58 | object CompiletimeParsers {
59 | val parser: GraphQLParser = GraphQLParser(GraphQLParser.defaultConfig)
60 | val schemaParser: SchemaParser = SchemaParser(parser)
61 | }
62 |
--------------------------------------------------------------------------------
/modules/core/src/main/scala-3/syntax3.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 |
18 | import cats.syntax.all._
19 | import org.typelevel.literally.Literally
20 | import grackle.Ast.Document
21 |
22 | trait VersionSpecificSyntax:
23 |
24 | extension (inline ctx: StringContext)
25 | inline def schema(inline args: Any*): Schema = ${SchemaLiteral('ctx, 'args)}
26 | inline def doc(inline args: Any*): Document = ${DocumentLiteral('ctx, 'args) }
27 |
28 | object SchemaLiteral extends Literally[Schema]:
29 | def validate(s: String)(using Quotes) =
30 | Schema(s, CompiletimeParsers.schemaParser).toEither.bimap(
31 | nec => s"Invalid schema:${nec.toList.distinct.mkString("\n 🐞 ", "\n 🐞 ", "\n")}",
32 | _ => '{Schema(${Expr(s)}, CompiletimeParsers.schemaParser).toOption.get}
33 | )
34 |
35 | object DocumentLiteral extends Literally[Document]:
36 | def validate(s: String)(using Quotes) =
37 | CompiletimeParsers.parser.parseText(s).toEither.bimap(
38 | _.fold(thr => show"Invalid document: ${thr.getMessage}", _.toList.mkString("\n 🐞 ", "\n 🐞 ", "\n")),
39 | _ => '{CompiletimeParsers.parser.parseText(${Expr(s)}).toOption.get}
40 | )
41 |
42 | object CompiletimeParsers:
43 | val parser: GraphQLParser = GraphQLParser(GraphQLParser.defaultConfig)
44 | val schemaParser: SchemaParser = SchemaParser(parser)
45 |
--------------------------------------------------------------------------------
/modules/core/src/main/scala/composedmapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 |
18 | import cats.MonadThrow
19 |
20 | import Cursor.AbstractCursor
21 | import syntax._
22 |
23 | abstract class ComposedMapping[F[_]](implicit val M: MonadThrow[F]) extends Mapping[F] {
24 | override def mkCursorForMappedField(parent: Cursor, fieldContext: Context, fm: FieldMapping): Result[Cursor] =
25 | ComposedCursor(fieldContext, parent.env).success
26 |
27 | case class ComposedCursor(context: Context, env: Env) extends AbstractCursor {
28 | val focus = null
29 | val parent = None
30 |
31 | def withEnv(env0: Env): Cursor = copy(env = env.add(env0))
32 |
33 | override def field(fieldName: String, resultName: Option[String]): Result[Cursor] =
34 | mkCursorForField(this, fieldName, resultName)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/modules/core/src/main/scala/jsonextractors.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 |
18 | import io.circe.Json
19 | import io.circe.JsonObject
20 |
21 | object JsonExtractor {
22 |
23 | object jsonNull {
24 | def unapply(j: Json): Option[Unit] = j.asNull
25 | }
26 |
27 | object jsonBoolean {
28 | def unapply(j: Json): Option[Boolean] = j.asBoolean
29 | }
30 |
31 | object jsonString {
32 | def unapply(j: Json): Option[String] = j.asString
33 | }
34 |
35 | object jsonInt {
36 | def unapply(j: Json): Option[Int] = j.asNumber.flatMap(_.toInt)
37 | }
38 |
39 | object jsonDouble {
40 | def unapply(j: Json): Option[Double] = j.asNumber.map(_.toDouble)
41 | }
42 |
43 | object jsonArray {
44 | def unapply(j: Json): Option[Vector[Json]] = j.asArray
45 | }
46 |
47 | object jsonObject {
48 | def unapply(j: Json): Option[JsonObject] = j.asObject
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/modules/core/src/main/scala/operation.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 |
18 | import syntax._
19 | import Query._
20 |
21 | sealed trait UntypedOperation {
22 | val name: Option[String]
23 | val query: Query
24 | val variables: UntypedVarDefs
25 | val directives: List[Directive]
26 | def rootTpe(schema: Schema): Result[NamedType] =
27 | this match {
28 | case _: UntypedOperation.UntypedQuery => schema.queryType.success
29 | case _: UntypedOperation.UntypedMutation => schema.mutationType.toResult("No mutation type defined in this schema.")
30 | case _: UntypedOperation.UntypedSubscription => schema.subscriptionType.toResult("No subscription type defined in this schema.")
31 | }
32 | }
33 | object UntypedOperation {
34 | case class UntypedQuery(
35 | name: Option[String],
36 | query: Query,
37 | variables: UntypedVarDefs,
38 | directives: List[Directive]
39 | ) extends UntypedOperation
40 | case class UntypedMutation(
41 | name: Option[String],
42 | query: Query,
43 | variables: UntypedVarDefs,
44 | directives: List[Directive]
45 | ) extends UntypedOperation
46 | case class UntypedSubscription(
47 | name: Option[String],
48 | query: Query,
49 | variables: UntypedVarDefs,
50 | directives: List[Directive]
51 | ) extends UntypedOperation
52 | }
53 |
54 | case class Operation(
55 | query: Query,
56 | rootTpe: NamedType,
57 | directives: List[Directive]
58 | )
59 |
--------------------------------------------------------------------------------
/modules/core/src/main/scala/problem.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 |
18 | import cats.Eq
19 | import io.circe._
20 | import io.circe.syntax._
21 |
22 | /** A problem, to be reported back to the user. */
23 | final case class Problem(
24 | message: String,
25 | locations: List[(Int, Int)] = Nil,
26 | path: List[String] = Nil,
27 | extensions: Option[JsonObject] = None,
28 | ) {
29 | override def toString = {
30 |
31 | lazy val pathText: String =
32 | path.mkString("/")
33 |
34 | lazy val locationsText: String =
35 | locations.map { case (a, b) =>
36 | if (a == b) a.toString else s"$a..$b"
37 | } .mkString(", ")
38 |
39 | val s = (path.nonEmpty, locations.nonEmpty) match {
40 | case (true, true) => s"$message (at $pathText: $locationsText)"
41 | case (true, false) => s"$message (at $pathText)"
42 | case (false, true) => s"$message (at $locationsText)"
43 | case (false, false) => message
44 | }
45 |
46 | extensions.fold(s)(obj => s"$s, extensions: ${obj.asJson.spaces2}")
47 |
48 | }
49 |
50 | }
51 |
52 | object Problem {
53 |
54 | implicit val ProblemEncoder: Encoder[Problem] = { p =>
55 |
56 | val locationsField: List[(String, Json)] =
57 | if (p.locations.isEmpty) Nil
58 | else List(
59 | "locations" ->
60 | p.locations.map { case (line, col) =>
61 | Json.obj(
62 | "line" -> line.asJson,
63 | "col" -> col.asJson
64 | )
65 | } .asJson
66 | )
67 |
68 | val pathField: List[(String, Json)] =
69 | if (p.path.isEmpty) Nil
70 | else List(("path" -> p.path.asJson))
71 |
72 | val extensionsField: List[(String, Json)] =
73 | p.extensions.fold(List.empty[(String, Json)])(obj => List("extensions" -> obj.asJson))
74 |
75 | Json.fromFields(
76 | "message" -> p.message.asJson ::
77 | locationsField :::
78 | pathField :::
79 | extensionsField
80 | )
81 |
82 | }
83 |
84 | implicit val eqProblem: Eq[Problem] =
85 | Eq.by(p => (p.message, p.locations, p.path))
86 | }
87 |
--------------------------------------------------------------------------------
/modules/core/src/main/scala/syntax.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 |
18 | object syntax extends VersionSpecificSyntax {
19 | implicit class ResultIdOps[A](val a: A) extends AnyVal {
20 | def success: Result[A] = Result.success(a)
21 | }
22 |
23 | implicit class ResultOptionOps[T](val opt: Option[T]) extends AnyVal {
24 | def toResult(ifNone: => Problem)(implicit dummy: DummyImplicit): Result[T] =
25 | opt.fold(Result.failure[T](ifNone))(Result.success)
26 |
27 | def toResult(ifNone: => String): Result[T] =
28 | opt.fold(Result.failure[T](ifNone))(Result.success)
29 |
30 | def toResultOrError(ifNone: => Throwable)(implicit dummy: DummyImplicit): Result[T] =
31 | opt.fold(Result.internalError[T](ifNone))(Result.success)
32 |
33 | def toResultOrError(ifNone: => String): Result[T] =
34 | opt.fold(Result.internalError[T](new Throwable(ifNone)))(Result.success)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/modules/core/src/main/scala/validationfailure.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 |
18 | import scala.io.AnsiColor
19 | import scala.util.control.NoStackTrace
20 |
21 | import cats._
22 | import cats.data.NonEmptyList
23 | import cats.implicits._
24 |
25 | import ValidationFailure.Severity
26 |
27 | abstract class ValidationFailure(val severity: Severity) extends AnsiColor {
28 | protected def formattedMessage: String
29 |
30 | private val prefix: String =
31 | severity match {
32 | case Severity.Error => "🛑 "
33 | case Severity.Warning => "⚠️ "
34 | case Severity.Info => "ℹ️ "
35 | }
36 |
37 | private val padding: String = " "*prefix.length
38 |
39 | protected def graphql(a: Any) = s"$BLUE$a$RESET"
40 | protected def scala(a: Any) = s"$RED$a$RESET"
41 | protected def key: String =
42 | s"Color Key: ${scala("◼")} Scala | ${graphql("◼")} GraphQL"
43 |
44 |
45 | final def toErrorMessage: String =
46 | s"""|$formattedMessage
47 | |$key
48 | |""".stripMargin.linesIterator.mkString(s"\n$prefix", s"\n$padding", s"\n$padding\n")
49 |
50 | protected def typeKind(tpe: Type): String =
51 | tpe.dealias match {
52 | case _: ObjectType => "object type"
53 | case _: InterfaceType => "interface type"
54 | case _: UnionType => "union type"
55 | case _: EnumType => "enum type"
56 | case _: ScalarType => "scalar type"
57 | case _ => "type"
58 | }
59 |
60 | protected def showType(tpe: Type): String = SchemaRenderer.renderType(tpe)
61 | protected def showNamedType(tpe: Type): String = tpe.underlyingNamed.name
62 | }
63 |
64 | object ValidationFailure {
65 | sealed trait Severity extends Product
66 | object Severity {
67 | case object Error extends Severity
68 | case object Warning extends Severity
69 | case object Info extends Severity
70 |
71 | implicit val OrderSeverity: Order[Severity] =
72 | Order.by {
73 | case Error => 3
74 | case Warning => 2
75 | case Info => 1
76 | }
77 | }
78 | }
79 |
80 | final case class ValidationException(failures: NonEmptyList[ValidationFailure]) extends RuntimeException with NoStackTrace {
81 | override def getMessage(): String =
82 | s"\n\n${failures.foldMap(_.toErrorMessage)}\n"
83 | }
84 |
--------------------------------------------------------------------------------
/modules/core/src/test/scala/compiler/AttributesSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package compiler
17 |
18 | import io.circe.literal._
19 | import munit.CatsEffectSuite
20 |
21 | final class AttributesSuite extends CatsEffectSuite {
22 | test("fields only") {
23 | val query = """
24 | query {
25 | itemByTag(tag: "A") {
26 | label
27 | tags
28 | tagCount
29 | }
30 | }
31 | """
32 |
33 | val expected = json"""
34 | {
35 | "data" : {
36 | "itemByTag" : [
37 | {
38 | "label" : "A",
39 | "tags" : [
40 | "A"
41 | ],
42 | "tagCount" : 1
43 | },
44 | {
45 | "label" : "AB",
46 | "tags" : [
47 | "A",
48 | "B"
49 | ],
50 | "tagCount" : 2
51 | }
52 | ]
53 | }
54 | }
55 | """
56 |
57 | val res = ItemMapping.compileAndRun(query)
58 |
59 | assertIO(res, expected)
60 | }
61 |
62 | test("no value attributes") {
63 | val query = """
64 | query {
65 | itemByTag(tag: "A") {
66 | label
67 | tagCountVA
68 | }
69 | }
70 | """
71 |
72 | val expected = json"""
73 | {
74 | "errors" : [
75 | {
76 | "message" : "No field 'tagCountVA' for type Item"
77 | }
78 | ]
79 | }
80 | """
81 |
82 | val res = ItemMapping.compileAndRun(query)
83 |
84 | assertIO(res, expected)
85 | }
86 |
87 | test("no cursor attributes") {
88 | val query = """
89 | query {
90 | itemByTag(tag: "A") {
91 | label
92 | tagCountCA
93 | }
94 | }
95 | """
96 |
97 | val expected = json"""
98 | {
99 | "errors" : [
100 | {
101 | "message" : "No field 'tagCountCA' for type Item"
102 | }
103 | ]
104 | }
105 | """
106 |
107 | val res = ItemMapping.compileAndRun(query)
108 |
109 | assertIO(res, expected)
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/modules/core/src/test/scala/compiler/PreserveArgsElaborator.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package compiler
17 |
18 | import grackle._
19 | import Query._
20 | import QueryCompiler._
21 |
22 | object PreserveArgsElaborator extends SelectElaborator {
23 | case class Preserved(args: List[Binding], directives: List[Directive])
24 |
25 | def subst(query: Query, fieldName: String, preserved: Preserved): Query = {
26 | def loop(query: Query): Query =
27 | query match {
28 | case Select(`fieldName`, alias, child) =>
29 | UntypedSelect(fieldName, alias, preserved.args, directives = preserved.directives, child)
30 | case Environment(env, child) if env.contains("preserved") => loop(child)
31 | case e@Environment(_, child) => e.copy(child = loop(child))
32 | case g: Group => g.copy(queries = g.queries.map(loop))
33 | case t@TransformCursor(_, child) => t.copy(child = loop(child))
34 | case other => other
35 | }
36 |
37 | loop(query)
38 | }
39 |
40 | override def transform(query: Query): Elab[Query] = {
41 | query match {
42 | case UntypedSelect(fieldName, _, _, _, _) =>
43 | for {
44 | t <- super.transform(query)
45 | preserved <- Elab.envE[Preserved]("preserved")
46 | } yield subst(t, fieldName, preserved)
47 |
48 | case other => super.transform(other)
49 | }
50 | }
51 |
52 | def select(ref: TypeRef, name: String, args: List[Binding], directives: List[Directive]): Elab[Unit] =
53 | Elab.env("preserved", Preserved(args, directives))
54 | }
55 |
--------------------------------------------------------------------------------
/modules/core/src/test/scala/compiler/TestMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package compiler
17 |
18 | import cats.MonadThrow
19 | import cats.effect.IO
20 |
21 | import grackle._
22 |
23 | abstract class TestMapping(implicit val M: MonadThrow[IO]) extends Mapping[IO] {
24 | val typeMappings: TypeMappings = TypeMappings.empty
25 | }
26 |
--------------------------------------------------------------------------------
/modules/core/src/test/scala/effects/ValueEffectData.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package effects
17 |
18 | import cats.effect.Sync
19 | import cats.implicits._
20 | import fs2.concurrent.SignallingRef
21 |
22 | import grackle._
23 | import grackle.syntax._
24 |
25 | class ValueEffectMapping[F[_]: Sync](ref: SignallingRef[F, Int]) extends ValueMapping[F] {
26 | val schema =
27 | schema"""
28 | type Query {
29 | foo: Struct!
30 | }
31 | type Struct {
32 | n: Int!
33 | s: String!
34 | }
35 | """
36 |
37 | val QueryType = schema.ref("Query")
38 | val StructType = schema.ref("Struct")
39 |
40 | case class Struct(n: Int, s: String)
41 |
42 | val typeMappings = List(
43 | ObjectMapping(
44 | tpe = QueryType,
45 | fieldMappings =
46 | List(
47 | // Compute a ValueCursor
48 | RootEffect.computeCursor("foo")((p, e) =>
49 | ref.update(_+1).as(
50 | Result(valueCursor(p, e, Struct(42, "hi")))
51 | )
52 | )
53 | )
54 | ),
55 | ValueObjectMapping[Struct](
56 | tpe = StructType,
57 | fieldMappings =
58 | List(
59 | ValueField("n", _.n),
60 | ValueField("s", _.s),
61 | )
62 | )
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/modules/core/src/test/scala/effects/ValueEffectSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package effects
17 |
18 | import cats.effect.IO
19 | import fs2.concurrent.SignallingRef
20 | import io.circe.Json
21 | import io.circe.literal._
22 | import munit.CatsEffectSuite
23 |
24 | final class ValueEffectSuite extends CatsEffectSuite {
25 | test("value effect") {
26 | val query = """
27 | query {
28 | foo {
29 | s,
30 | n
31 | }
32 | }
33 | """
34 |
35 | val expected = json"""
36 | {
37 | "data" : {
38 | "foo" : {
39 | "s" : "hi",
40 | "n" : 42
41 | }
42 | }
43 | }
44 | """
45 |
46 | val prg: IO[(Json, Int)] =
47 | for {
48 | ref <- SignallingRef[IO, Int](0)
49 | map = new ValueEffectMapping(ref)
50 | res <- map.compileAndRun(query)
51 | eff <- ref.get
52 | } yield (res, eff)
53 |
54 | assertIO(prg, (expected, 1))
55 | }
56 |
57 | test("value effect, aliased") {
58 | val query = """
59 | query {
60 | quux:foo {
61 | s,
62 | n
63 | }
64 | }
65 | """
66 |
67 | val expected = json"""
68 | {
69 | "data" : {
70 | "quux" : {
71 | "s" : "hi",
72 | "n" : 42
73 | }
74 | }
75 | }
76 | """
77 |
78 | val prg: IO[(Json, Int)] =
79 | for {
80 | ref <- SignallingRef[IO, Int](0)
81 | map = new ValueEffectMapping(ref)
82 | res <- map.compileAndRun(query)
83 | eff <- ref.get
84 | } yield (res, eff)
85 |
86 | assertIO(prg, (expected, 1))
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/modules/core/src/test/scala/laws/ResultSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package laws
17 |
18 | import cats.Eq
19 | import cats.data.NonEmptyChain
20 | import cats.kernel.laws.discipline.{EqTests, SemigroupTests}
21 | import cats.laws.discipline.{ApplicativeTests, MonadErrorTests, ParallelTests, TraverseTests}
22 | import cats.laws.discipline.arbitrary._
23 | import munit.DisciplineSuite
24 | import org.scalacheck.{Arbitrary, Cogen, Gen}
25 | import org.scalacheck.Arbitrary.{arbitrary => getArbitrary}
26 |
27 | import grackle.{Problem, Result}
28 |
29 | class ResultSuite extends DisciplineSuite {
30 | implicit val eqThrow: Eq[Throwable] = Eq.fromUniversalEquals
31 |
32 | implicit val grackleLawsArbitraryForProblem: Arbitrary[Problem] =
33 | Arbitrary(getArbitrary[String].map(Problem(_)))
34 |
35 | implicit val grackleLawsCogenForProblem: Cogen[Problem] =
36 | Cogen[String].contramap(_.message)
37 |
38 | implicit def grackleArbitraryFnForResult[T](implicit arbF: Arbitrary[T => T]): Arbitrary[Result[T] => Result[T]] =
39 | Arbitrary(arbF.arbitrary.map(f => (r: Result[T]) => r.map(f)))
40 |
41 | implicit def grackleLawsArbitraryForResult[T](implicit T: Arbitrary[T]): Arbitrary[Result[T]] =
42 | Arbitrary(
43 | Gen.oneOf(
44 | T.arbitrary.map(Result.Success(_)),
45 | for {
46 | ps <- getArbitrary[NonEmptyChain[Problem]]
47 | t <- T.arbitrary
48 | } yield Result.Warning(ps, t),
49 | getArbitrary[NonEmptyChain[Problem]].map(Result.Failure(_)),
50 | getArbitrary[Throwable].map(Result.InternalError(_))
51 | )
52 | )
53 |
54 | checkAll("MonadError[Result] @ Int", MonadErrorTests[Result, Either[Throwable, NonEmptyChain[Problem]]].monadError[Int, Int, Int])
55 |
56 | checkAll("Traverse[Result] @ Int with Option", TraverseTests[Result].traverse[Int, Int, Int, Int, Option, Option])
57 |
58 | checkAll("Parallel[Result] @ Int", ParallelTests[Result].parallel[Either[Throwable, NonEmptyChain[Problem]], Int])
59 |
60 | checkAll("Semigroup[Result[List[T: Semigroup]]]", SemigroupTests[Result[List[Int]]].semigroup)
61 |
62 | checkAll("Eq[Result[Int]]", EqTests[Result[Int]].eqv)
63 |
64 | checkAll("Applicative[ResultT] @ Int", ApplicativeTests[Result].applicative[Int, Int, Int])
65 | }
66 |
--------------------------------------------------------------------------------
/modules/docs/src/main/scala/grackle/Output.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.docs
17 |
18 | import java.nio.charset.StandardCharsets
19 | import java.nio.file.{Files, Path}
20 |
21 | import cats.effect.IO
22 | import cats.effect.unsafe.implicits.global
23 |
24 | object Output {
25 | def snip(path: String, tag: String): String = {
26 | val header = "```scala"
27 | val footer = "```"
28 |
29 | val prg =
30 | for {
31 | txt <- IO.blocking(Files.readString(Path.of(path), StandardCharsets.UTF_8))
32 | } yield {
33 | val tagged = txt.split("\n").dropWhile(!_.contains(tag)).drop(1).takeWhile(!_.contains(tag)).mkString("\n")
34 | s"$header\n$tagged\n$footer"
35 | }
36 |
37 | prg.unsafeRunSync()
38 | }
39 |
40 | def header(variant: String): String = {
41 | variant match {
42 | case "repo" =>
43 | val prg = IO.blocking(Files.readString(Path.of("header.md"), StandardCharsets.UTF_8))
44 | prg.unsafeRunSync()
45 | case "tutorial" =>
46 | "# Grackle"
47 | case _ => ""
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/modules/doobie-core/src/main/scala/DoobieMappingCompanion.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package doobie
18 |
19 | import _root_.doobie.util.transactor.Transactor
20 | import cats.effect.Sync
21 | import org.typelevel.log4cats.Logger
22 |
23 | trait DoobieMappingCompanion {
24 | def mkMapping[F[_]: Sync](transactor: Transactor[F], monitor: DoobieMonitor[F]): Mapping[F]
25 |
26 | def mkMapping[F[_] : Sync](transactor: Transactor[F]): Mapping[F] = {
27 | val monitor: DoobieMonitor[F] = DoobieMonitor.noopMonitor[F]
28 |
29 | mkMapping(transactor, monitor)
30 | }
31 |
32 | @deprecated("Use mkMapping instead", "0.2.0")
33 | def fromTransactor[F[_] : Sync](transactor: Transactor[F]): Mapping[F] =
34 | mkMapping(transactor)
35 | }
36 |
37 | trait LoggedDoobieMappingCompanion {
38 | def mkMapping[F[_]: Sync](transactor: Transactor[F], monitor: DoobieMonitor[F]): Mapping[F]
39 |
40 | def mkMapping[F[_] : Sync : Logger](transactor: Transactor[F]): Mapping[F] = {
41 | val monitor: DoobieMonitor[F] = DoobieMonitor.loggerMonitor[F](Logger[F])
42 |
43 | mkMapping(transactor, monitor)
44 | }
45 |
46 | @deprecated("Use mkMapping instead", "0.2.0")
47 | def fromTransactor[F[_] : Sync : Logger](transactor: Transactor[F]): Mapping[F] =
48 | mkMapping(transactor)
49 | }
50 |
--------------------------------------------------------------------------------
/modules/doobie-core/src/main/scala/DoobieMonitor.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package doobie
18 |
19 | import _root_.doobie.Fragment
20 | import cats.Applicative
21 | import cats.implicits._
22 | import grackle.QueryInterpreter.ProtoJson
23 | import org.typelevel.log4cats.Logger
24 | import grackle.sql.SqlStatsMonitor
25 | import cats.effect.Ref
26 | import cats.effect.Sync
27 |
28 | case class DoobieStats(
29 | query: Query,
30 | sql: String,
31 | args: List[Any],
32 | rows: Int,
33 | cols: Int
34 | )
35 |
36 | object DoobieMonitor {
37 |
38 | def noopMonitor[F[_]: Applicative]: DoobieMonitor[F] =
39 | new DoobieMonitor[F] {
40 | def queryMapped(query: Query, fragment: Fragment, rows: Int, cols: Int): F[Unit] = ().pure[F]
41 | def resultComputed(result: Result[ProtoJson]): F[Unit] = ().pure[F]
42 | }
43 |
44 | def loggerMonitor[F[_]](logger: Logger[F]): DoobieMonitor[F] =
45 | new DoobieMonitor[F] {
46 |
47 | def queryMapped(query: Query, fragment: Fragment, rows: Int, cols: Int): F[Unit] =
48 | logger.info(
49 | s"""query: $query
50 | |sql: ${fragment.internals.sql}
51 | |args: ${fragment.internals.elements.mkString(", ")}
52 | |fetched $rows row(s) of $cols column(s)
53 | """.stripMargin)
54 |
55 | def resultComputed(result: Result[ProtoJson]): F[Unit] =
56 | logger.info(s"result: $result")
57 | }
58 |
59 | def statsMonitor[F[_]: Sync]: F[SqlStatsMonitor[F, Fragment]] =
60 | Ref[F].of(List.empty[SqlStatsMonitor.SqlStats]).map { ref =>
61 | new SqlStatsMonitor[F, Fragment](ref) {
62 | def inspect(fragment: Fragment): (String, List[Any]) =
63 | (fragment.internals.sql, fragment.internals.elements)
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/modules/doobie-core/src/main/scala/package.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 |
18 | import grackle.sql.SqlMonitor
19 | import _root_.doobie.Fragment
20 |
21 | package object doobie {
22 | type DoobieMonitor[F[_]] = SqlMonitor[F, Fragment]
23 | }
24 |
--------------------------------------------------------------------------------
/modules/doobie-core/src/test/scala/DoobieDatabaseSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.doobie
17 | package test
18 |
19 | import java.time.Duration
20 |
21 | import doobie.Meta
22 | import io.circe.{Decoder => CDecoder, Encoder => CEncoder}
23 | import munit.CatsEffectSuite
24 |
25 | import grackle.sql.test._
26 |
27 | trait DoobieDatabaseSuite extends CatsEffectSuite {
28 | trait DoobieTestMapping[F[_]] extends DoobieMappingLike[F] with SqlTestMapping[F] {
29 |
30 | type TestCodec[T] = (Meta[T], Boolean)
31 |
32 | def bool: TestCodec[Boolean] = (Meta[Boolean], false)
33 | def text: TestCodec[String] = (Meta[String], false)
34 | def varchar: TestCodec[String] = (Meta[String], false)
35 | def bpchar(len: Int): TestCodec[String] = (Meta[String], false)
36 | def int2: TestCodec[Int] = (Meta[Int], false)
37 | def int4: TestCodec[Int] = (Meta[Int], false)
38 | def int8: TestCodec[Long] = (Meta[Long], false)
39 | def float4: TestCodec[Float] = (Meta[Float], false)
40 | def float8: TestCodec[Double] = (Meta[Double], false)
41 | def numeric(precision: Int, scale: Int): TestCodec[BigDecimal] = (Meta[BigDecimal], false)
42 |
43 | def duration: TestCodec[Duration] = (Meta[Long].timap(Duration.ofMillis)(_.toMillis), false)
44 |
45 | def nullable[T](c: TestCodec[T]): TestCodec[T] = (c._1, true)
46 |
47 | def list[T: CDecoder : CEncoder](c: TestCodec[T]): TestCodec[List[T]] = {
48 | val cm = c._1
49 | val decode = cm.get.get.k.asInstanceOf[String => T]
50 | val encode = cm.put.put.k.asInstanceOf[T => String]
51 | val cl = Meta.Advanced.array[String]("VARCHAR", "_VARCHAR").imap(_.toList.map(decode))(_.map(encode).toArray)
52 | (cl, false)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/modules/doobie-mssql/src/test/resources/scripts/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Start SQL Server
4 | /opt/mssql/bin/sqlservr &
5 |
6 | # Wait for SQL Server to start (max 90 seconds)
7 | for i in {1..90}; do
8 | /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P Test_123_Test -No -Q 'SELECT 1' &> /dev/null
9 | if [ $? -eq 0 ]; then
10 | echo "SQL Server is up"
11 | break
12 | fi
13 | echo "Waiting for SQL Server to start..."
14 | sleep 1
15 | done
16 |
17 | # Initialize the database
18 | if [ ! -f /tmp/healthy ]; then
19 | echo "Intializing the database ..."
20 | /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P Test_123_Test -No \
21 | -i <(cat /container-entrypoint-initdb.d/init.sql /grackle-initdb.d/*.sql)
22 |
23 | /bin/touch /tmp/healthy
24 |
25 | echo "Database initialized"
26 | fi
27 |
28 | # Keep the container running
29 | tail -f /dev/null
30 |
--------------------------------------------------------------------------------
/modules/doobie-mssql/src/test/resources/scripts/init.sql:
--------------------------------------------------------------------------------
1 | USE master;
2 | GO
3 |
4 | CREATE DATABASE test
5 | COLLATE Latin1_General_100_CI_AS_SC_UTF8;
6 | GO
7 |
8 | USE test;
9 | GO
10 |
--------------------------------------------------------------------------------
/modules/doobie-oracle/src/test/resources/scripts/01_create_user.sql:
--------------------------------------------------------------------------------
1 | ALTER SESSION SET CONTAINER=FREEPDB1;
2 |
3 | CREATE USER TEST IDENTIFIED BY test QUOTA UNLIMITED ON USERS;
4 |
5 | GRANT CONNECT, RESOURCE TO TEST;
6 |
--------------------------------------------------------------------------------
/modules/doobie-oracle/src/test/resources/scripts/02_create_tables.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ ! -f /tmp/healthy ]; then
4 | echo "Intializing the database ..."
5 | sqlplus -s test/test@//localhost/FREEPDB1 < <(cat /grackle-initdb.d/*.sql)
6 |
7 | /bin/touch /tmp/healthy
8 |
9 | echo "Database initialized"
10 | fi
11 |
--------------------------------------------------------------------------------
/modules/doobie-pg/src/main/scala/DoobiePgMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.doobie.postgres
17 |
18 | import cats.effect.Sync
19 | import _root_.doobie.Transactor
20 |
21 | import grackle.Mapping
22 | import grackle.doobie._
23 | import grackle.sqlpg._
24 |
25 | abstract class DoobiePgMapping[F[_]](
26 | val transactor: Transactor[F],
27 | val monitor: DoobieMonitor[F],
28 | )(
29 | implicit val M: Sync[F]
30 | ) extends Mapping[F] with DoobiePgMappingLike[F]
31 |
32 | trait DoobiePgMappingLike[F[_]] extends DoobieMappingLike[F] with SqlPgMappingLike[F]
33 |
--------------------------------------------------------------------------------
/modules/doobie-pg/src/test/scala/DoobiePgDatabaseSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.doobie.postgres
17 | package test
18 |
19 | import java.time.{LocalDate, LocalTime, OffsetDateTime}
20 | import java.util.UUID
21 |
22 | import cats.effect.{IO, Resource, Sync}
23 | import doobie.{Get, Meta, Put, Transactor}
24 | import doobie.postgres.implicits._
25 | import doobie.postgres.circe.jsonb.implicits._
26 | import io.circe.Json
27 | import munit.catseffect.IOFixture
28 |
29 | import grackle.doobie.DoobieMonitor
30 | import grackle.doobie.test.DoobieDatabaseSuite
31 | import grackle.sql.test._
32 | import grackle.sqlpg.test._
33 |
34 | trait DoobiePgDatabaseSuite extends DoobieDatabaseSuite with SqlPgDatabaseSuite {
35 | abstract class DoobiePgTestMapping[F[_]: Sync](transactor: Transactor[F], monitor: DoobieMonitor[F] = DoobieMonitor.noopMonitor[IO])
36 | extends DoobiePgMapping[F](transactor, monitor) with DoobieTestMapping[F] with SqlTestMapping[F] {
37 | def uuid: TestCodec[UUID] = (Meta[UUID], false)
38 | def localDate: TestCodec[LocalDate] = (Meta[LocalDate], false)
39 | def localTime: TestCodec[LocalTime] = (Meta[LocalTime], false)
40 | def offsetDateTime: TestCodec[OffsetDateTime] = (Meta[OffsetDateTime], false)
41 | def jsonb: TestCodec[Json] = (new Meta(Get[Json], Put[Json]), false)
42 | def nvarchar: TestCodec[String] = (Meta[String], false)
43 | }
44 |
45 | def transactorResource: Resource[IO, Transactor[IO]] = {
46 | val connInfo = postgresConnectionInfo
47 | import connInfo._
48 |
49 | Resource.pure(
50 | Transactor.fromDriverManager[IO](
51 | driverClassName,
52 | jdbcUrl,
53 | username,
54 | password,
55 | None
56 | )
57 | )
58 | }
59 |
60 | val transactorFixture: IOFixture[Transactor[IO]] = ResourceSuiteLocalFixture("doobiepg", transactorResource)
61 | override def munitFixtures: Seq[IOFixture[_]] = Seq(transactorFixture)
62 |
63 | def transactor: Transactor[IO] = transactorFixture()
64 | }
65 |
--------------------------------------------------------------------------------
/modules/generic/src/main/scala/genericmapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package generic
18 |
19 | import cats.MonadThrow
20 | import org.tpolecat.sourcepos.SourcePos
21 |
22 | import syntax._
23 | import Cursor.DeferredCursor
24 |
25 | abstract class GenericMapping[F[_]](implicit val M: MonadThrow[F]) extends Mapping[F] with GenericMappingLike[F]
26 |
27 | trait GenericMappingLike[F[_]] extends ScalaVersionSpecificGenericMappingLike[F] {
28 | def genericCursor[T](path: Path, env: Env, t: T)(implicit cb: => CursorBuilder[T]): Result[Cursor] =
29 | if(path.isRoot)
30 | cb.build(Context(path.rootTpe), t, None, env)
31 | else
32 | DeferredCursor(path, (context, parent) => cb.build(context, t, Some(parent), env)).success
33 |
34 | override def mkCursorForMappedField(parent: Cursor, fieldContext: Context, fm: FieldMapping): Result[Cursor] =
35 | fm match {
36 | case GenericField(_, t, cb, _) =>
37 | cb().build(fieldContext, t, Some(parent), parent.env)
38 | case _ =>
39 | super.mkCursorForMappedField(parent, fieldContext, fm)
40 | }
41 |
42 | case class GenericField[T](val fieldName: String, t: T, cb: () => CursorBuilder[T], hidden: Boolean)(
43 | implicit val pos: SourcePos
44 | ) extends FieldMapping {
45 | def subtree: Boolean = true
46 | }
47 |
48 | def GenericField[T](fieldName: String, t: T, hidden: Boolean = false)(implicit cb: => CursorBuilder[T], pos: SourcePos): GenericField[T] =
49 | new GenericField(fieldName, t, () => cb, hidden)
50 |
51 | object semiauto {
52 | final def deriveObjectCursorBuilder[T](tpe: Type)
53 | (implicit mkBuilder: => MkObjectCursorBuilder[T]): ObjectCursorBuilder[T] = mkBuilder(tpe)
54 | final def deriveInterfaceCursorBuilder[T](tpe: Type)
55 | (implicit mkBuilder: => MkInterfaceCursorBuilder[T]): CursorBuilder[T] = mkBuilder(tpe)
56 | }
57 |
58 | trait ObjectCursorBuilder[T] extends CursorBuilder[T] {
59 | def renameField(from: String, to: String): ObjectCursorBuilder[T]
60 | def transformFieldNames(f: String => String): ObjectCursorBuilder[T]
61 | def transformField[U](fieldName: String)(f: T => Result[U])(implicit cb: => CursorBuilder[U]): ObjectCursorBuilder[T]
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/modules/generic/src/test/scala/EffectsSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package generic
18 |
19 | import cats.effect.{IO, Sync}
20 | import cats.implicits._
21 | import fs2.concurrent.SignallingRef
22 | import io.circe.Json
23 | import io.circe.literal._
24 | import munit.CatsEffectSuite
25 |
26 | import grackle.syntax._
27 |
28 | class GenericEffectMapping[F[_]: Sync](ref: SignallingRef[F, Int]) extends GenericMapping[F] {
29 | import semiauto._
30 |
31 | val schema =
32 | schema"""
33 | type Query {
34 | foo: Struct!
35 | }
36 | type Struct {
37 | n: Int!
38 | s: String!
39 | }
40 | """
41 |
42 | val QueryType = schema.ref("Query")
43 | val StructType = schema.ref("Struct")
44 |
45 | case class Struct(n: Int, s: String)
46 | object Struct {
47 | implicit val cursorBuilder: CursorBuilder[Struct] =
48 | deriveObjectCursorBuilder[Struct](StructType)
49 | }
50 |
51 | val typeMappings = List(
52 | ObjectMapping(
53 | tpe = QueryType,
54 | fieldMappings =
55 | List(
56 | // Compute a ValueCursor
57 | RootEffect.computeCursor("foo")((p, e) =>
58 | ref.update(_+1).as(
59 | genericCursor(p, e, Struct(42, "hi"))
60 | )
61 | )
62 | )
63 | )
64 | )
65 | }
66 |
67 | final class EffectSuite extends CatsEffectSuite {
68 | test("generic effect") {
69 | val query = """
70 | query {
71 | foo {
72 | s,
73 | n
74 | }
75 | }
76 | """
77 |
78 | val expected = json"""
79 | {
80 | "data" : {
81 | "foo" : {
82 | "s" : "hi",
83 | "n" : 42
84 | }
85 | }
86 | }
87 | """
88 |
89 | val prg: IO[(Json, Int)] =
90 | for {
91 | ref <- SignallingRef[IO, Int](0)
92 | map = new GenericEffectMapping(ref)
93 | res <- map.compileAndRun(query)
94 | eff <- ref.get
95 | } yield (res, eff)
96 |
97 | assertIO(prg, (expected, 1))
98 | }
99 |
100 | test("generic effect, aliased") {
101 | val query = """
102 | query {
103 | quux:foo {
104 | s,
105 | n
106 | }
107 | }
108 | """
109 |
110 | val expected = json"""
111 | {
112 | "data" : {
113 | "quux" : {
114 | "s" : "hi",
115 | "n" : 42
116 | }
117 | }
118 | }
119 | """
120 |
121 | val prg: IO[(Json, Int)] =
122 | for {
123 | ref <- SignallingRef[IO, Int](0)
124 | map = new GenericEffectMapping(ref)
125 | res <- map.compileAndRun(query)
126 | eff <- ref.get
127 | } yield (res, eff)
128 |
129 | assertIO(prg, (expected, 1))
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/modules/skunk/js-jvm/src/test/scala/subscription/SubscriptionSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.skunk.test.subscription
17 |
18 | import scala.concurrent.duration._
19 |
20 | import cats.effect.IO
21 | import cats.implicits._
22 | import io.circe.Json
23 | import io.circe.literal._
24 | import skunk.implicits._
25 |
26 | import grackle.skunk.test.SkunkDatabaseSuite
27 |
28 | class SubscriptionSuite extends SkunkDatabaseSuite {
29 |
30 | lazy val mapping = SubscriptionMapping.mkMapping(pool)
31 |
32 | test("subscription driven by a Postgres channel") {
33 |
34 | val query = """
35 | subscription {
36 | channel {
37 | name
38 | country {
39 | name
40 | }
41 | }
42 | }
43 | """
44 |
45 | val expected = List(
46 | json"""
47 | {
48 | "data" : {
49 | "channel" : {
50 | "name" : "Godoy Cruz",
51 | "country" : {
52 | "name" : "Argentina"
53 | }
54 | }
55 | }
56 | }
57 | """,
58 | json"""
59 | {
60 | "data" : {
61 | "channel" : {
62 | "name" : "Posadas",
63 | "country" : {
64 | "name" : "Argentina"
65 | }
66 | }
67 | }
68 | }
69 | """,
70 | )
71 |
72 | val prog: IO[List[Json]] =
73 | for {
74 |
75 | // start a fiber that subscibes and takes the first two notifications
76 | fi <- mapping.compileAndRunSubscription(query).take(2).compile.toList.start
77 |
78 | // We're racing now, so wait a sec before starting notifications
79 | _ <- IO.sleep(1.second)
80 |
81 | // Send some notifications through Postgres, which will trigger queries on the subscription.
82 | _ <- pool.use { s =>
83 | val ch = s.channel(id"city_channel").contramap[Int](_.toString)
84 | List(101, 102, 103).traverse_(ch.notify)
85 | }
86 |
87 | // Now rejoin the fiber
88 | out <- fi.join
89 | js <- out.embedNever
90 |
91 | } yield js
92 |
93 | // Done
94 | assertIO(prog, expected)
95 |
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/modules/skunk/shared/src/main/scala/SkunkMappingCompanion.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package skunk
18 |
19 | import _root_.skunk.Session
20 | import cats.effect.{ Resource, Sync }
21 |
22 | trait SkunkMappingCompanion {
23 |
24 | def mkMapping[F[_]: Sync](pool: Resource[F, Session[F]], monitor: SkunkMonitor[F]): Mapping[F]
25 |
26 | final def mkMapping[F[_]: Sync](pool: Resource[F, Session[F]]): Mapping[F] =
27 | mkMapping(pool, SkunkMonitor.noopMonitor)
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/modules/skunk/shared/src/main/scala/SkunkMonitor.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package skunk
18 |
19 | import cats.Applicative
20 | import cats.implicits._
21 | import cats.effect.Sync
22 | import cats.effect.Ref
23 | import _root_.skunk.AppliedFragment
24 |
25 | import QueryInterpreter.ProtoJson
26 | import sql._
27 |
28 | case class SkunkStats(
29 | query: Query,
30 | sql: String,
31 | args: Any,
32 | rows: Int,
33 | cols: Int
34 | )
35 |
36 | object SkunkMonitor {
37 |
38 | def noopMonitor[F[_]: Applicative]: SkunkMonitor[F] =
39 | new SkunkMonitor[F] {
40 | def queryMapped(query: Query, fragment: AppliedFragment, rows: Int, cols: Int): F[Unit] = ().pure[F]
41 | def resultComputed(result: Result[ProtoJson]): F[Unit] = ().pure[F]
42 | }
43 |
44 | def statsMonitor[F[_]: Sync]: F[SqlStatsMonitor[F, AppliedFragment]] =
45 | Ref[F].of(List.empty[SqlStatsMonitor.SqlStats]).map { ref =>
46 | new SqlStatsMonitor[F, AppliedFragment](ref) {
47 | def inspect(af: AppliedFragment): (String, List[Any]) = {
48 | val (f, a) = (af.fragment, af.argument)
49 | (f.sql, f.encoder.encode(a).map(_.getOrElse("null")))
50 | }
51 | }
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/modules/skunk/shared/src/main/scala/package.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 |
18 | import grackle.sql.SqlMonitor
19 | import _root_.skunk.AppliedFragment
20 |
21 | package object skunk {
22 | type SkunkMonitor[F[_]] = SqlMonitor[F, AppliedFragment]
23 | }
24 |
--------------------------------------------------------------------------------
/modules/sql-core/src/main/scala-2/Like.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package sql
18 |
19 | import scala.util.matching.Regex
20 |
21 | import syntax._
22 |
23 | case class Like private[sql] (x: Term[_], pattern: String, caseInsensitive: Boolean) extends Predicate {
24 | lazy val r = Like.likeToRegex(pattern, caseInsensitive)
25 | def apply(c: Cursor): Result[Boolean] =
26 | x(c).flatMap(_ match {
27 | case s: String => r.matches(s).success
28 | case Some(s: String) => r.matches(s).success
29 | case None => false.success
30 | case other => Result.internalError(s"Expected value of type String or Option[String], found $other")
31 | })
32 | def children = List(x)
33 | }
34 |
35 | object Like extends Like0 {
36 | private[sql] def apply(x: Term[_], pattern: String, caseInsensitive: Boolean): Like =
37 | new Like(x, pattern, caseInsensitive)
38 |
39 | private def likeToRegex(pattern: String, caseInsensitive: Boolean): Regex = {
40 | val csr = ("^"+pattern.replace("%", ".*").replace("_", ".")+"$")
41 | (if (caseInsensitive) s"(?i:$csr)" else csr).r
42 | }
43 | }
44 |
45 | trait Like0 {
46 | trait PossiblyOptionString[T]
47 | object PossiblyOptionString extends PossiblyOptionString0 {
48 | implicit val sInst: PossiblyOptionString[String] = new PossiblyOptionString[String] {}
49 | }
50 | trait PossiblyOptionString0 {
51 | implicit val osInst: PossiblyOptionString[Option[String]] = new PossiblyOptionString[Option[String]] {}
52 | }
53 |
54 | def apply[T](x: Term[T], pattern: String, caseInsensitive: Boolean)(implicit ev: PossiblyOptionString[T]): Predicate =
55 | new Like(x, pattern, caseInsensitive)
56 | }
57 |
--------------------------------------------------------------------------------
/modules/sql-core/src/main/scala-3/Like.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package sql
18 |
19 | import scala.util.matching.Regex
20 |
21 | case class Like(x: Term[String]|Term[Option[String]], pattern: String, caseInsensitive: Boolean) extends Predicate {
22 | lazy val r = Like.likeToRegex(pattern, caseInsensitive)
23 | def apply(c: Cursor): Result[Boolean] =
24 | x(c).map(_ match {
25 | case s: String => r.matches(s)
26 | case Some(s: String) => r.matches(s)
27 | case None => false
28 | })
29 | def children = List(x)
30 | }
31 |
32 | object Like {
33 | private def likeToRegex(pattern: String, caseInsensitive: Boolean): Regex = {
34 | val csr = ("^"+pattern.replace("%", ".*").replace("_", ".")+"$")
35 | (if (caseInsensitive) s"(?i:$csr)" else csr).r
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/modules/sql-core/src/main/scala/FailedJoin.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql
17 |
18 | /** A sentinal value representing the empty column values from a failed join. */
19 | case object FailedJoin
20 |
--------------------------------------------------------------------------------
/modules/sql-core/src/main/scala/SqlModule.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package sql
18 |
19 | import cats.{Monoid, Reducible}
20 |
21 | /** These are the bits that are specific to the underlying database layer. */
22 | trait SqlModule[F[_]] {
23 |
24 | def monitor: SqlMonitor[F, Fragment]
25 |
26 | /** The type of a codec that reads and writes column values of type `A`. */
27 | type Codec
28 |
29 | /** The type of an encoder that writes column values of type `A`. */
30 | type Encoder
31 |
32 | /** Extract an encoder from a codec. */
33 | def toEncoder(c: Codec): Encoder
34 |
35 | def isNullable(c: Codec): Boolean
36 |
37 | /** Typeclass for SQL fragments. */
38 | trait SqlFragment[T] extends Monoid[T] {
39 |
40 | def bind[A](encoder: Encoder, value: A): T
41 |
42 | def const(s: String): T
43 |
44 | /** Returns `(f1) AND (f2) AND ... (fn)` for all fragments. */
45 | def and(fs: T*): T
46 |
47 | /** Returns `(f1) AND (f2) AND ... (fn)` for all defined fragments. */
48 | def andOpt(fs: Option[T]*): T
49 |
50 | /** Returns `(f1) OR (f2) OR ... (fn)` for all defined fragments. */
51 | def orOpt(fs: Option[T]*): T
52 |
53 | /** Returns `WHERE (f1) AND (f2) AND ... (fn)` or the empty fragment if `fs` is empty. */
54 | def whereAnd(fs: T*): T
55 |
56 | /** Returns `WHERE (f1) AND (f2) AND ... (fn)` for defined `f`, if any, otherwise the empty fragment. */
57 | def whereAndOpt(fs: Option[T]*): T
58 |
59 | def in[G[_]: Reducible, A](f: T, fs: G[A], enc: Encoder): T
60 |
61 | def parentheses(f: T): T
62 |
63 | def needsCollation(codec: Codec): Boolean
64 |
65 | def sqlTypeName(codec: Codec): Option[String]
66 | }
67 |
68 | /** The type of a fragment of SQL together with any interpolated arguments. */
69 | type Fragment
70 |
71 | implicit def Fragments: SqlFragment[Fragment]
72 |
73 | def intEncoder: Encoder
74 | def stringEncoder: Encoder
75 | def booleanEncoder: Encoder
76 | def doubleEncoder: Encoder
77 |
78 | def intCodec: Codec
79 |
80 | def fetch(fragment: Fragment, codecs: List[(Boolean, Codec)]): F[Vector[Array[Any]]]
81 | }
82 |
--------------------------------------------------------------------------------
/modules/sql-core/src/main/scala/SqlMonitor.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package sql
18 |
19 | import QueryInterpreter.ProtoJson
20 |
21 | /** Monitor for a `SqlMapping` in `F` with fragments of type `A`. */
22 | trait SqlMonitor[F[_], A] {
23 | def queryMapped(query: Query, fragment: A, rows: Int, cols: Int): F[Unit]
24 | def resultComputed(result: Result[ProtoJson]): F[Unit]
25 | }
26 |
--------------------------------------------------------------------------------
/modules/sql-core/src/main/scala/SqlStatsMonitor.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package sql
18 |
19 | import cats.Applicative
20 | import cats.effect.Ref
21 | import cats.syntax.all._
22 |
23 | import SqlStatsMonitor.SqlStats
24 |
25 | /**
26 | * A SqlMonitor that accumulates `SqlStats` in a `Ref`. Stage boundaries and results are not
27 | * tracked.
28 | */
29 | abstract class SqlStatsMonitor[F[_]: Applicative, A](
30 | ref: Ref[F, List[SqlStats]]
31 | ) extends SqlMonitor[F, A] {
32 |
33 | /** Get the current state and reset it to `Nil`. */
34 | final def take: F[List[SqlStats]] =
35 | ref.getAndSet(Nil).map(_.reverse)
36 |
37 | final def queryMapped(query: Query, fragment: A, rows: Int, cols: Int): F[Unit] =
38 | ref.update { stats =>
39 | val (sql, args) = inspect(fragment)
40 | SqlStats(query, sql, args, rows, cols) :: stats
41 | }
42 |
43 | final def resultComputed(result: Result[QueryInterpreter.ProtoJson]): F[Unit] =
44 | Applicative[F].unit
45 |
46 | /** Extract the SQL string and query arguments from a fragment. */
47 | def inspect(fragment: A): (String, List[Any])
48 |
49 | }
50 |
51 | object SqlStatsMonitor {
52 |
53 | final case class SqlStats(
54 | val query: Query,
55 | val sql: String,
56 | val args: List[Any],
57 | val rows: Int,
58 | val cols: Int
59 | ) {
60 |
61 | /** Normalize whitespace in `query` for easier testing. */
62 | def normalize: SqlStats =
63 | copy(sql = sql.replaceAll("\\s+", " ").trim)
64 |
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlArrayJoinMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import grackle.syntax._
19 |
20 | trait SqlArrayJoinMapping[F[_]] extends SqlTestMapping[F] {
21 |
22 | object root extends TableDef("array_join_root") {
23 | val id = col("id", varchar)
24 | }
25 |
26 | object listA extends TableDef("array_join_list_a") {
27 | val id = col("id", varchar)
28 | val rootId = col("root_id", nullable(varchar))
29 | val aElem = col("a_elem", nullable(list(varchar)))
30 | }
31 |
32 | object listB extends TableDef("array_join_list_b") {
33 | val id = col("id", varchar)
34 | val rootId = col("root_id", nullable(varchar))
35 | val bElem = col("b_elem", nullable(int4))
36 | }
37 |
38 | val schema =
39 | schema"""
40 | type Query {
41 | root: [Root!]!
42 | }
43 | type Root {
44 | id: String!
45 | listA: [ElemA!]!
46 | listB: [ElemB!]!
47 | }
48 | type ElemA {
49 | id: String!
50 | elemA: [String!]
51 | }
52 | type ElemB {
53 | id: String!
54 | elemB: Int
55 | }
56 | """
57 |
58 | val QueryType = schema.ref("Query")
59 | val RootType = schema.ref("Root")
60 | val ElemAType = schema.ref("ElemA")
61 | val ElemBType = schema.ref("ElemB")
62 |
63 | val typeMappings =
64 | List(
65 | ObjectMapping(
66 | tpe = QueryType,
67 | fieldMappings =
68 | List(
69 | SqlObject("root")
70 | )
71 | ),
72 | ObjectMapping(
73 | tpe = RootType,
74 | fieldMappings =
75 | List(
76 | SqlField("id", root.id, key = true),
77 | SqlObject("listA", Join(root.id, listA.rootId)),
78 | SqlObject("listB", Join(root.id, listB.rootId))
79 | )
80 | ),
81 | ObjectMapping(
82 | tpe = ElemAType,
83 | fieldMappings =
84 | List(
85 | SqlField("id", listA.id, key = true),
86 | SqlField("rootId", listA.rootId, hidden = true),
87 | SqlField("elemA", listA.aElem)
88 | )
89 | ),
90 | ObjectMapping(
91 | tpe = ElemBType,
92 | fieldMappings =
93 | List(
94 | SqlField("id", listB.id, key = true),
95 | SqlField("rootId", listB.rootId, hidden = true),
96 | SqlField("elemB", listB.bElem)
97 | )
98 | )
99 | )
100 | }
101 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlCompositeKeyMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import grackle.syntax._
19 |
20 | trait SqlCompositeKeyMapping[F[_]] extends SqlTestMapping[F] {
21 |
22 | object compositeKeyParent extends TableDef("composite_key_parent") {
23 | val key1 = col("key_1", int4)
24 | val key2 = col("key_2", varchar)
25 | }
26 |
27 | object compositeKeyChild extends TableDef("composite_key_child") {
28 | val id = col("id", int4)
29 | val parent1 = col("parent_1", int4)
30 | val parent2 = col("parent_2", varchar)
31 | }
32 |
33 | val schema =
34 | schema"""
35 | type Query {
36 | parents: [Parent!]!
37 | }
38 | type Parent {
39 | key1: Int!
40 | key2: String!
41 | children: [Child!]!
42 | }
43 | type Child {
44 | id: Int!
45 | parent1: Int!
46 | parent2: String!
47 | }
48 | """
49 |
50 | val QueryType = schema.ref("Query")
51 | val ParentType = schema.ref("Parent")
52 | val ChildType = schema.ref("Child")
53 |
54 | val typeMappings =
55 | List(
56 | ObjectMapping(
57 | tpe = QueryType,
58 | fieldMappings =
59 | List(
60 | SqlObject("parents")
61 | )
62 | ),
63 | ObjectMapping(
64 | tpe = ParentType,
65 | fieldMappings =
66 | List(
67 | SqlField("key1", compositeKeyParent.key1, key = true),
68 | SqlField("key2", compositeKeyParent.key2, key = true),
69 | SqlObject("children", Join(List((compositeKeyParent.key1, compositeKeyChild.parent1), (compositeKeyParent.key2, compositeKeyChild.parent2))))
70 | )
71 | ),
72 | ObjectMapping(
73 | tpe = ChildType,
74 | fieldMappings =
75 | List(
76 | SqlField("id", compositeKeyChild.id, key = true),
77 | SqlField("parent1", compositeKeyChild.parent1),
78 | SqlField("parent2", compositeKeyChild.parent2),
79 | )
80 | )
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlCompositeKeySuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.effect.IO
19 | import io.circe.literal._
20 | import munit.CatsEffectSuite
21 |
22 | import grackle._
23 |
24 | import grackle.test.GraphQLResponseTests.assertWeaklyEqualIO
25 |
26 | trait SqlCompositeKeySuite extends CatsEffectSuite {
27 | def mapping: Mapping[IO]
28 |
29 | test("root query") {
30 | val query = """
31 | query {
32 | parents {
33 | key1
34 | key2
35 | children {
36 | id
37 | parent1
38 | parent2
39 | }
40 | }
41 | }
42 | """
43 |
44 | val expected = json"""
45 | {
46 | "data" : {
47 | "parents" : [
48 | {
49 | "key1" : 2,
50 | "key2" : "bar",
51 | "children" : [
52 | {
53 | "id" : 4,
54 | "parent1" : 2,
55 | "parent2" : "bar"
56 | }
57 | ]
58 | },
59 | {
60 | "key1" : 2,
61 | "key2" : "foo",
62 | "children" : [
63 | {
64 | "id" : 3,
65 | "parent1" : 2,
66 | "parent2" : "foo"
67 | }
68 | ]
69 | },
70 | {
71 | "key1" : 1,
72 | "key2" : "bar",
73 | "children" : [
74 | {
75 | "id" : 2,
76 | "parent1" : 1,
77 | "parent2" : "bar"
78 | }
79 | ]
80 | },
81 | {
82 | "key1" : 1,
83 | "key2" : "foo",
84 | "children" : [
85 | {
86 | "id" : 1,
87 | "parent1" : 1,
88 | "parent2" : "foo"
89 | }
90 | ]
91 | }
92 | ]
93 | }
94 | }
95 | """
96 |
97 | val res = mapping.compileAndRun(query)
98 |
99 | assertWeaklyEqualIO(res, expected)
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlCursorJsonMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.implicits._
19 | import io.circe.Json
20 | import io.circe.syntax.EncoderOps
21 |
22 | import grackle._
23 | import syntax._
24 | import Query._
25 | import Predicate._
26 | import Value._
27 | import QueryCompiler._
28 |
29 | trait SqlCursorJsonMapping[F[_]] extends SqlTestMapping[F] {
30 |
31 | object brands extends TableDef("brands") {
32 | val id = col("id", int4)
33 | val category = col("categories", int4)
34 | }
35 |
36 | trait Category {
37 | val name: String
38 | }
39 | object Category {
40 | def decodeCategoryInt(i: Int): List[Category] =
41 | i match {
42 | case 8 => List(Film)
43 | case 16 => List(Drama)
44 | }
45 | implicit val categoryEncoder: io.circe.Encoder[Category] =
46 | io.circe.Encoder.instance {
47 | case Film => Json.obj(("name", Json.fromString("Film")))
48 | case Drama => Json.obj(("name", Json.fromString("Drama")))
49 | case _ => Json.Null
50 | }
51 | object Film extends Category {
52 | override val name: String = "Film"
53 | }
54 | object Drama extends Category {
55 | override val name: String = "Drama"
56 | }
57 | }
58 |
59 | val schema =
60 | schema"""
61 | type Query {
62 | brands(id: Int!): Brand
63 | }
64 | type Brand {
65 | id: Int!
66 | categories: [Category!]
67 | }
68 | type Category {
69 | name: String
70 | }
71 | """
72 |
73 | val QueryType = schema.ref("Query")
74 | val BrandType = schema.ref("Brand")
75 | val CategoryType = schema.ref("Category")
76 |
77 | def decodeCategories(c: Cursor): Result[Json] = {
78 | for {
79 | enc <- c.fieldAs[Int]("encodedCategories")
80 | res = Category.decodeCategoryInt(enc)
81 | } yield res.asJson
82 | }
83 |
84 | val typeMappings =
85 | List(
86 | ObjectMapping(
87 | tpe = QueryType,
88 | fieldMappings =
89 | List(
90 | SqlObject("brands")
91 | )
92 | ),
93 | ObjectMapping(
94 | tpe = BrandType,
95 | fieldMappings =
96 | List(
97 | SqlField("id", brands.id, key = true),
98 | SqlField("encodedCategories", brands.category, hidden = true),
99 | CursorFieldJson("categories", decodeCategories, List("encodedCategories"))
100 | )
101 | )
102 | )
103 |
104 | override val selectElaborator = SelectElaborator {
105 | case (QueryType, "brands", List(Binding("id", IntValue(id)))) =>
106 | Elab.transformChild(child => Unique(Filter(Eql(BrandType / "id", Const(id)), child)))
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlCursorJsonSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.effect.IO
19 | import io.circe.literal._
20 | import munit.CatsEffectSuite
21 |
22 | import grackle._
23 | import grackle.test.GraphQLResponseTests.assertWeaklyEqualIO
24 |
25 | trait SqlCursorJsonSuite extends CatsEffectSuite {
26 | def mapping: Mapping[IO]
27 |
28 | test("cursor field returns json") {
29 | val query =
30 | """
31 | query {
32 | brands(id: 1) {
33 | categories {
34 | name
35 | }
36 | }
37 | }
38 | """
39 |
40 | val expected =
41 | json"""
42 | {
43 | "data" : {
44 | "brands" : {
45 | "categories" : [
46 | {
47 | "name" : "Film"
48 | }
49 | ]
50 | }
51 | }
52 | }
53 | """
54 |
55 | val res = mapping.compileAndRun(query)
56 |
57 | assertWeaklyEqualIO(res, expected)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlEmbedding2Mapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import grackle._
19 | import Predicate._
20 | import Query._
21 | import QueryCompiler._
22 | import Value._
23 | import syntax._
24 |
25 | trait SqlEmbedding2Mapping[F[_]] extends SqlTestMapping[F] {
26 |
27 | object ProgramTable extends TableDef("t_program") {
28 | val Id = col("c_program_id", varchar)
29 | }
30 |
31 | object ObservationTable extends TableDef("t_observation") {
32 | val Pid = col("c_program_id", varchar)
33 | val Id = col("c_observation_id", varchar)
34 | }
35 |
36 | val schema = schema"""
37 | type Query {
38 | program(programId: String!): Program
39 | }
40 | type Program {
41 | id: String!
42 | observations: ObservationSelectResult!
43 | }
44 | type ObservationSelectResult {
45 | matches: [Observation!]!
46 | }
47 | type Observation {
48 | id: String!
49 | }
50 | """
51 |
52 | val QueryType = schema.ref("Query")
53 | val ProgramType = schema.ref("Program")
54 | val ObservationSelectResultType = schema.ref("ObservationSelectResult")
55 | val ObservationType = schema.ref("Observation")
56 |
57 | val typeMappings =
58 | List(
59 | ObjectMapping(
60 | QueryType,
61 | List(
62 | SqlObject("program")
63 | )
64 | ),
65 | ObjectMapping(
66 | ProgramType,
67 | List(
68 | SqlField("id", ProgramTable.Id, key = true),
69 | SqlObject("observations")
70 | )
71 | ),
72 | ObjectMapping(
73 | ObservationSelectResultType,
74 | List(
75 | SqlField("id", ProgramTable.Id, key = true, hidden = true),
76 | SqlObject("matches", Join(ProgramTable.Id, ObservationTable.Pid)),
77 | )
78 | ),
79 | ObjectMapping(
80 | ObservationType,
81 | List(
82 | SqlField("id", ObservationTable.Id, key = true),
83 | )
84 | )
85 | )
86 |
87 | override val selectElaborator = SelectElaborator {
88 | case (QueryType, "program", List(Binding("programId", StringValue(id)))) =>
89 | Elab.transformChild(child => Unique(Filter(Eql(ProgramType / "id", Const(id)), child)))
90 |
91 | case (ObservationSelectResultType, "matches", Nil) =>
92 | Elab.transformChild(child => OrderBy(OrderSelections(List(OrderSelection[String](ObservationType / "id", true, true))), child))
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlEmbedding2Suite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.effect.IO
19 | import io.circe.literal._
20 | import munit.CatsEffectSuite
21 |
22 | import grackle._
23 |
24 | import grackle.test.GraphQLResponseTests.assertWeaklyEqualIO
25 |
26 | trait SqlEmbedding2Suite extends CatsEffectSuite {
27 | def mapping: Mapping[IO]
28 |
29 | test("paging") {
30 | val query = """
31 | query {
32 | program(programId: "foo") {
33 | id
34 | observations {
35 | matches {
36 | id
37 | }
38 | }
39 | }
40 | }
41 | """
42 |
43 | val expected = json"""
44 | {
45 | "data" : {
46 | "program" : {
47 | "id" : "foo",
48 | "observations" : {
49 | "matches" : [
50 | {
51 | "id" : "fo1"
52 | },
53 | {
54 | "id" : "fo2"
55 | }
56 | ]
57 | }
58 | }
59 | }
60 | }
61 | """
62 |
63 | val res = mapping.compileAndRun(query)
64 |
65 | assertWeaklyEqualIO(res, expected)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlEmbedding3Mapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import grackle.syntax._
19 |
20 | trait SqlEmbedding3Mapping[F[_]] extends SqlTestMapping[F] {
21 |
22 | object Bogus extends RootDef {
23 | val Id = col("", varchar)
24 | }
25 |
26 | object ObservationTable extends TableDef("t_observation") {
27 | val Pid = col("c_program_id", varchar)
28 | val Id = col("c_observation_id", varchar)
29 | }
30 |
31 | val schema = schema"""
32 | type Query {
33 | observations: ObservationSelectResult!
34 | }
35 | type ObservationSelectResult {
36 | matches: [Observation!]!
37 | }
38 | type Observation {
39 | id: String!
40 | }
41 | """
42 |
43 | val QueryType = schema.ref("Query")
44 | val ObservationSelectResultType = schema.ref("ObservationSelectResult")
45 | val ObservationType = schema.ref("Observation")
46 |
47 | val typeMappings =
48 | List(
49 | ObjectMapping(
50 | QueryType,
51 | List(
52 | SqlObject("observations")
53 | )
54 | ),
55 | ObjectMapping(
56 | ObservationSelectResultType,
57 | List(
58 | SqlField("", Bogus.Id, hidden = true),
59 | SqlObject("matches"),
60 | )
61 | ),
62 | ObjectMapping(
63 | ObservationType,
64 | List(
65 | SqlField("id", ObservationTable.Id, key = true),
66 | )
67 | )
68 | )
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlEmbedding3Suite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.effect.IO
19 | import io.circe.literal._
20 | import munit.CatsEffectSuite
21 |
22 | import grackle._
23 |
24 | import grackle.test.GraphQLResponseTests.assertWeaklyEqualIO
25 |
26 | trait SqlEmbedding3Suite extends CatsEffectSuite {
27 | def mapping: Mapping[IO]
28 |
29 | test("paging") {
30 | val query = """
31 | query {
32 | observations {
33 | matches {
34 | id
35 | }
36 | }
37 | }
38 | """
39 |
40 | val expected = json"""
41 | {
42 | "data" : {
43 | "observations" : {
44 | "matches" : [
45 | {
46 | "id" : "fo1"
47 | },
48 | {
49 | "id" : "bo2"
50 | },
51 | {
52 | "id" : "bo1"
53 | },
54 | {
55 | "id" : "fo2"
56 | }
57 | ]
58 | }
59 | }
60 | }
61 | """
62 |
63 | val res = mapping.compileAndRun(query)
64 |
65 | assertWeaklyEqualIO(res, expected)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlFilterJoinAliasSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.effect.IO
19 | import io.circe.literal._
20 | import munit.CatsEffectSuite
21 |
22 | import grackle._
23 |
24 | import grackle.test.GraphQLResponseTests.assertWeaklyEqualIO
25 |
26 | trait SqlFilterJoinAliasSuite extends CatsEffectSuite {
27 | def mapping: Mapping[IO]
28 |
29 | test("base query") {
30 | val query = """
31 | query {
32 | episode(id: "a") {
33 | images(filter: { name: "abc" }) {
34 | inner { name }
35 | }
36 | }
37 | }
38 | """
39 |
40 | val expected = json"""
41 | {
42 | "data" : {
43 | "episode" : {
44 | "images" : [
45 | {
46 | "inner" : {
47 | "name" : "abc"
48 | }
49 | }
50 | ]
51 | }
52 | }
53 | }
54 | """
55 |
56 | val res = mapping.compileAndRun(query)
57 |
58 | assertWeaklyEqualIO(res, expected)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlGraphMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.implicits._
19 |
20 | import grackle._
21 | import grackle.syntax._
22 | import Query._, Predicate._, Value._
23 | import QueryCompiler._
24 |
25 | trait SqlGraphMapping[F[_]] extends SqlTestMapping[F] {
26 |
27 | object graphNode extends TableDef("graph_node") {
28 | val id = col("id", int4)
29 | }
30 |
31 | object graphEdge extends TableDef("graph_edge") {
32 | val id = col("id", int4)
33 | val a = col("a", nullable(int4))
34 | val b = col("b", nullable(int4))
35 | }
36 |
37 | val schema =
38 | schema"""
39 | type Query {
40 | node(id: Int!): Node
41 | edge(id: Int!): Edge
42 | }
43 | type Node {
44 | id: Int!
45 | neighbours: [Node!]!
46 | }
47 | type Edge {
48 | id: Int!
49 | a: Node
50 | b: Node
51 | }
52 | """
53 |
54 | val QueryType = schema.ref("Query")
55 | val NodeType = schema.ref("Node")
56 | val EdgeType = schema.ref("Edge")
57 |
58 | val typeMappings =
59 | List(
60 | ObjectMapping(
61 | tpe = QueryType,
62 | fieldMappings =
63 | List(
64 | SqlObject("node"),
65 | SqlObject("edge")
66 | )
67 | ),
68 | ObjectMapping(
69 | tpe = NodeType,
70 | fieldMappings =
71 | List(
72 | SqlField("id", graphNode.id, key = true),
73 | SqlObject("neighbours", Join(graphNode.id, graphEdge.a), Join(graphEdge.b, graphNode.id))
74 | )
75 | ),
76 | ObjectMapping(
77 | tpe = EdgeType,
78 | fieldMappings =
79 | List(
80 | SqlField("id", graphEdge.id, key = true),
81 | SqlObject("a", Join(graphEdge.a, graphNode.id)),
82 | SqlObject("b", Join(graphEdge.b, graphNode.id))
83 | )
84 | )
85 | )
86 |
87 | override val selectElaborator = SelectElaborator {
88 | case (QueryType, "node", List(Binding("id", IntValue(id)))) =>
89 | Elab.transformChild(child => Unique(Filter(Eql(NodeType / "id", Const(id)), child)))
90 | case (QueryType, "edge", List(Binding("id", IntValue(id)))) =>
91 | Elab.transformChild(child => Unique(Filter(Eql(EdgeType / "id", Const(id)), child)))
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlGraphSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.effect.IO
19 | import io.circe.literal._
20 | import munit.CatsEffectSuite
21 |
22 | import grackle._
23 |
24 | import grackle.test.GraphQLResponseTests.assertWeaklyEqualIO
25 |
26 | trait SqlGraphSuite extends CatsEffectSuite {
27 | def mapping: Mapping[IO]
28 |
29 | test("root query") {
30 | val query = """
31 | query {
32 | node(id: 1) {
33 | id
34 | }
35 | }
36 | """
37 |
38 | val expected = json"""
39 | {
40 | "data" : {
41 | "node" : {
42 | "id" : 1
43 | }
44 | }
45 | }
46 | """
47 |
48 | val res = mapping.compileAndRun(query)
49 |
50 | assertWeaklyEqualIO(res, expected)
51 | }
52 |
53 | test("neighbour query (1)") {
54 | val query = """
55 | query {
56 | node(id: 1) {
57 | id
58 | neighbours {
59 | id
60 | }
61 | }
62 | }
63 | """
64 |
65 | val expected = json"""
66 | {
67 | "data" : {
68 | "node" : {
69 | "id" : 1,
70 | "neighbours" : [
71 | {
72 | "id" : 2
73 | },
74 | {
75 | "id" : 3
76 | }
77 | ]
78 | }
79 | }
80 | }
81 | """
82 |
83 | val res = mapping.compileAndRun(query)
84 |
85 | assertWeaklyEqualIO(res, expected)
86 | }
87 |
88 | test("neighbour query (2)") {
89 | val query = """
90 | query {
91 | node(id: 1) {
92 | id
93 | neighbours {
94 | id
95 | neighbours {
96 | id
97 | }
98 | }
99 | }
100 | }
101 | """
102 |
103 | val expected = json"""
104 | {
105 | "data" : {
106 | "node" : {
107 | "id" : 1,
108 | "neighbours" : [
109 | {
110 | "id" : 2,
111 | "neighbours" : [
112 | {
113 | "id" : 4
114 | }
115 | ]
116 | },
117 | {
118 | "id" : 3,
119 | "neighbours" : [
120 | {
121 | "id" : 4
122 | }
123 | ]
124 | }
125 | ]
126 | }
127 | }
128 | }
129 | """
130 |
131 | val res = mapping.compileAndRun(query)
132 |
133 | assertWeaklyEqualIO(res, expected)
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlInterfacesMapping2.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import grackle._
19 | import grackle.syntax._
20 | import Predicate._
21 |
22 | trait SqlInterfacesMapping2[F[_]] extends SqlInterfacesMapping[F] { self =>
23 |
24 | override lazy val entityTypeDiscriminator = new SqlDiscriminator {
25 |
26 | // discriminator always fails
27 | def discriminate(c: Cursor): Result[Type] =
28 | Result.internalError("Boom!!!")
29 |
30 | // same as in SqlInterfacesMapping
31 | def narrowPredicate(subtpe: Type): Result[Predicate] = {
32 | def mkPredicate(tpe: EntityType): Result[Predicate] =
33 | Eql(EType / "entityType", Const(tpe)).success
34 |
35 | subtpe match {
36 | case FilmType => mkPredicate(EntityType.Film)
37 | case SeriesType => mkPredicate(EntityType.Series)
38 | case _ => Result.internalError(s"Invalid discriminator: $subtpe")
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlInterfacesSuite2.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.effect.IO
19 | import munit.CatsEffectSuite
20 |
21 | import grackle._
22 |
23 | trait SqlInterfacesSuite2 extends CatsEffectSuite {
24 | def mapping: Mapping[IO]
25 |
26 | test("when discriminator fails the entire query should fail") {
27 | val query = """
28 | query {
29 | entities {
30 | id
31 | entityType
32 | title
33 | ... on Film {
34 | rating
35 | }
36 | ... on Series {
37 | numberOfEpisodes
38 | }
39 | }
40 | }
41 | """
42 |
43 | val expected = Left("Boom!!!")
44 |
45 | val res = mapping.compileAndRun(query).attempt
46 |
47 | assertIO(res.map(_.left.map(_.getMessage)), expected)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlJsonbMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.implicits._
19 |
20 | import grackle._
21 | import syntax._
22 |
23 | import Query._
24 | import Predicate._
25 | import Value._
26 | import QueryCompiler._
27 |
28 | trait SqlJsonbMapping[F[_]] extends SqlTestMapping[F] {
29 |
30 | object records extends TableDef("records") {
31 | val id = col("id", int4)
32 | val record = col("record", nullable(jsonb))
33 | }
34 |
35 | val schema =
36 | schema"""
37 | type Query {
38 | record(id: Int!): Row
39 | records: [Row!]!
40 | }
41 | type Row {
42 | id: Int!
43 | record: Record
44 | nonNullRecord: Record!
45 | }
46 | type Record {
47 | bool: Boolean
48 | int: Int
49 | float: Float
50 | string: String
51 | id: ID
52 | choice: Choice
53 | arrary: [Int!]
54 | object: A
55 | children: [Child!]!
56 | }
57 | enum Choice {
58 | ONE
59 | TWO
60 | THREE
61 | }
62 | interface Child {
63 | id: ID
64 | }
65 | type A implements Child {
66 | id: ID
67 | aField: Int
68 | }
69 | type B implements Child {
70 | id: ID
71 | bField: String
72 | }
73 | """
74 |
75 | val QueryType = schema.ref("Query")
76 | val RowType = schema.ref("Row")
77 | val RecordType = schema.ref("Record")
78 |
79 | val typeMappings =
80 | List(
81 | ObjectMapping(
82 | tpe = QueryType,
83 | fieldMappings =
84 | List(
85 | SqlObject("record"),
86 | SqlObject("records")
87 | )
88 | ),
89 | ObjectMapping(
90 | tpe = RowType,
91 | fieldMappings =
92 | List(
93 | SqlField("id", records.id, key = true),
94 | SqlJson("record", records.record),
95 | SqlJson("nonNullRecord", records.record)
96 | )
97 | ),
98 | )
99 |
100 | override val selectElaborator = SelectElaborator {
101 | case (QueryType, "record", List(Binding("id", IntValue(id)))) =>
102 | Elab.transformChild(child => Unique(Filter(Eql(RowType / "id", Const(id)), child)))
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlMappingValidatorValidSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.effect.IO
19 | import cats.implicits._
20 | import grackle.sql._
21 | import munit.CatsEffectSuite
22 |
23 | trait SqlMappingValidatorValidSuite extends CatsEffectSuite {
24 | def mapping: SqlMappingLike[IO]
25 |
26 | lazy val M = mapping
27 |
28 | test("valid mapping is valid") {
29 | val es = M.validate()
30 |
31 | es match {
32 | case Nil => ()
33 | case _ => fail(es.foldMap(_.toErrorMessage))
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlMixedMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import java.util.UUID
19 |
20 | import scala.util.Try
21 |
22 | import cats.implicits._
23 | import io.circe.literal._
24 |
25 | import grackle._
26 | import syntax._
27 | import Query._
28 | import Predicate._
29 | import Value._
30 | import QueryCompiler._
31 |
32 | // Mapping illustrating arbitrary mapping mixins with root query fields
33 | // defined by sql, value and circe mappings.
34 | trait SqlMixedMapping[F[_]] extends SqlTestMapping[F] with ValueMappingLike[F] { self =>
35 | object movies extends TableDef("movies") {
36 | val id = col("id", uuid)
37 | val title = col("title", text)
38 | }
39 |
40 | val schema =
41 | schema"""
42 | type Query {
43 | movie(id: UUID!): Movie
44 | movies: [Movie!]!
45 | foo: Foo!
46 | bar: Bar
47 | }
48 | type Movie {
49 | id: UUID!
50 | title: String!
51 | nested: Bar
52 | genre: String!
53 | rating: Float!
54 | }
55 | type Foo {
56 | value: Int!
57 | }
58 | type Bar {
59 | message: String
60 | }
61 | scalar UUID
62 | """
63 |
64 | val QueryType = schema.ref("Query")
65 | val MovieType = schema.ref("Movie")
66 | val FooType = schema.ref("Foo")
67 | val BarType = schema.ref("Bar")
68 | val UUIDType = schema.ref("UUID")
69 |
70 | case class Foo(value: Int)
71 |
72 | val typeMappings =
73 | List(
74 | ObjectMapping(
75 | tpe = QueryType,
76 | fieldMappings =
77 | List(
78 | SqlObject("movie"),
79 | SqlObject("movies"),
80 | ValueField[Unit]("foo", _ => Foo(23)),
81 | CirceField("bar", json"""{ "message": "Hello world" }""")
82 | )
83 | ),
84 | ObjectMapping(
85 | tpe = MovieType,
86 | fieldMappings =
87 | List(
88 | SqlField("id", movies.id, key = true),
89 | SqlField("title", movies.title),
90 | CirceField("nested", json"""{ "message": "Hello world nested" }"""),
91 | ValueField[Unit]("genre", _ => "comedy"),
92 | CirceField("rating", json"""7.8""")
93 | )
94 | ),
95 | ValueObjectMapping[Foo](
96 | tpe = FooType,
97 | fieldMappings =
98 | List(
99 | ValueField("value", _.value)
100 | )
101 | ),
102 | LeafMapping[UUID](UUIDType)
103 | )
104 |
105 | object UUIDValue {
106 | def unapply(s: StringValue): Option[UUID] =
107 | Try(UUID.fromString(s.value)).toOption
108 | }
109 |
110 | override val selectElaborator = SelectElaborator {
111 | case (QueryType, "movie", List(Binding("id", UUIDValue(id)))) =>
112 | Elab.transformChild(child => Unique(Filter(Eql(MovieType / "id", Const(id)), child)))
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlMutationSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.effect.IO
19 | import io.circe.literal._
20 | import munit.CatsEffectSuite
21 |
22 | import grackle._
23 |
24 | import grackle.test.GraphQLResponseTests.assertWeaklyEqualIO
25 |
26 | trait SqlMutationSuite extends CatsEffectSuite {
27 |
28 | def mapping: Mapping[IO]
29 |
30 | test("simple read") {
31 | val query = """
32 | query {
33 | city(id: 2) {
34 | name
35 | population
36 | country {
37 | name
38 | }
39 | }
40 | }
41 | """
42 |
43 | val expected = json"""
44 | {
45 | "data" : {
46 | "city" : {
47 | "name" : "Qandahar",
48 | "population" : 237500,
49 | "country" : {
50 | "name" : "Afghanistan"
51 | }
52 | }
53 | }
54 | }
55 | """
56 |
57 | val res = mapping.compileAndRun(query)
58 |
59 | assertWeaklyEqualIO(res, expected)
60 | }
61 |
62 | // In this test the query is fully elaborated prior to execution.
63 | test("simple update") {
64 | val query = """
65 | mutation {
66 | updatePopulation(id: 2, population: 12345) {
67 | name
68 | population
69 | country {
70 | name
71 | }
72 | }
73 | }
74 | """
75 |
76 | val expected = json"""
77 | {
78 | "data" : {
79 | "updatePopulation" : {
80 | "name" : "Qandahar",
81 | "population" : 12345,
82 | "country" : {
83 | "name" : "Afghanistan"
84 | }
85 | }
86 | }
87 | }
88 | """
89 |
90 | val res = mapping.compileAndRun(query)
91 |
92 | assertWeaklyEqualIO(res, expected)
93 | }
94 |
95 | // In this test the query must be elaborated *after* execution of the effect because the ID
96 | // of the inserted city isn't known until then.
97 | test("insert") {
98 | val query = """
99 | mutation {
100 | createCity(name: "Wiggum", countryCode: "USA", population: 789) {
101 | name
102 | population
103 | country {
104 | name
105 | }
106 | }
107 | }
108 | """
109 |
110 | val expected = json"""
111 | {
112 | "data" : {
113 | "createCity" : {
114 | "name" : "Wiggum",
115 | "population" : 789,
116 | "country" : {
117 | "name" : "United States"
118 | }
119 | }
120 | }
121 | }
122 | """
123 |
124 | val res = mapping.compileAndRun(query)
125 |
126 | assertWeaklyEqualIO(res, expected)
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlRecursiveInterfacesSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.effect.IO
19 | import io.circe.literal._
20 | import munit.CatsEffectSuite
21 |
22 | import grackle._
23 |
24 | import grackle.test.GraphQLResponseTests.assertWeaklyEqualIO
25 |
26 | trait SqlRecursiveInterfacesSuite extends CatsEffectSuite {
27 | def mapping: Mapping[IO]
28 |
29 | test("specialized query on both sides") {
30 | val query = """
31 | query {
32 | items {
33 | id
34 | ... on ItemA {
35 | nextItem {
36 | id
37 | }
38 | }
39 | ... on ItemB {
40 | nextItem {
41 | id
42 | }
43 | }
44 | }
45 | }
46 | """
47 |
48 | val expected = json"""
49 | {
50 | "data" : {
51 | "items" : [
52 | {
53 | "id" : "1",
54 | "nextItem" : {
55 | "id" : "2"
56 | }
57 | },
58 | {
59 | "id" : "2",
60 | "nextItem" : {
61 | "id" : "1"
62 | }
63 | }
64 | ]
65 | }
66 | }
67 | """
68 |
69 | val res = mapping.compileAndRun(query)
70 |
71 | assertWeaklyEqualIO(res, expected)
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlSiblingListsSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.effect.IO
19 | import io.circe.literal._
20 | import munit.CatsEffectSuite
21 |
22 | import grackle._
23 |
24 | import grackle.test.GraphQLResponseTests.assertWeaklyEqualIO
25 |
26 | trait SqlSiblingListsSuite extends CatsEffectSuite {
27 | def mapping: Mapping[IO]
28 |
29 | test("base query") {
30 | val query = """
31 | query {
32 | a(id: "id_a_1") {
33 | bs {
34 | cs { nameC }
35 | ds { nameD }
36 | }
37 | }
38 | }
39 | """
40 |
41 | val expected = json"""
42 | {
43 | "data" : {
44 | "a" : {
45 | "bs" : [
46 | {
47 | "cs" : [
48 | {
49 | "nameC" : "name_c"
50 | }
51 | ],
52 | "ds" : [
53 | {
54 | "nameD" : "name_d"
55 | }
56 | ]
57 | }
58 | ]
59 | }
60 | }
61 | }
62 | """
63 |
64 | val res = mapping.compileAndRun(query)
65 | //println(res)
66 |
67 | assertWeaklyEqualIO(res, expected)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlTestMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import java.time.{Duration, LocalDate, LocalTime, OffsetDateTime}
19 | import java.util.UUID
20 |
21 | import io.circe.{Decoder => CDecoder, Encoder => CEncoder, Json}
22 | import org.tpolecat.sourcepos.SourcePos
23 | import org.tpolecat.typename.TypeName
24 |
25 | import grackle._
26 | import sql.SqlMappingLike
27 |
28 | trait SqlTestMapping[F[_]] extends SqlMappingLike[F] { outer =>
29 | type TestCodec[T] <: Codec
30 |
31 | def bool: TestCodec[Boolean]
32 | def text: TestCodec[String]
33 | def varchar: TestCodec[String]
34 | def nvarchar: TestCodec[String]
35 | def bpchar(len: Int): TestCodec[String]
36 | def int2: TestCodec[Int]
37 | def int4: TestCodec[Int]
38 | def int8: TestCodec[Long]
39 | def float4: TestCodec[Float]
40 | def float8: TestCodec[Double]
41 | def numeric(precision: Int, scale: Int): TestCodec[BigDecimal]
42 |
43 | def uuid: TestCodec[UUID]
44 | def localDate: TestCodec[LocalDate]
45 | def localTime: TestCodec[LocalTime]
46 | def offsetDateTime: TestCodec[OffsetDateTime]
47 | def duration: TestCodec[Duration]
48 |
49 | def jsonb: TestCodec[Json]
50 |
51 | def nullable[T](c: TestCodec[T]): TestCodec[T]
52 | def list[T: CDecoder : CEncoder](c: TestCodec[T]): TestCodec[List[T]]
53 |
54 | def col[T](colName: String, codec: TestCodec[T])(implicit tableName: TableName, typeName: TypeName[T], pos: SourcePos): ColumnRef =
55 | ColumnRef(tableName.name, colName, codec, typeName.value, pos)
56 | }
57 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlTreeMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import cats.implicits._
19 |
20 | import grackle._
21 | import grackle.syntax._
22 | import Query._, Predicate._, Value._
23 | import QueryCompiler._
24 |
25 | trait SqlTreeMapping[F[_]] extends SqlTestMapping[F] {
26 |
27 | object bintree extends TableDef("bintree") {
28 | val id = col("id", int4)
29 | val leftChild = col("left_child", nullable(int4))
30 | val rightChild = col("right_child", nullable(int4))
31 | }
32 |
33 | val schema =
34 | schema"""
35 | type Query {
36 | bintree(id: Int!): BinTree
37 | }
38 | type BinTree {
39 | id: Int!
40 | left: BinTree
41 | right: BinTree
42 | }
43 | """
44 |
45 | val QueryType = schema.ref("Query")
46 | val BinTreeType = schema.ref("BinTree")
47 |
48 | val typeMappings =
49 | List(
50 | ObjectMapping(
51 | tpe = QueryType,
52 | fieldMappings =
53 | List(
54 | SqlObject("bintree")
55 | )
56 | ),
57 | ObjectMapping(
58 | tpe = BinTreeType,
59 | fieldMappings =
60 | List(
61 | SqlField("id", bintree.id, key = true),
62 | SqlObject("left", Join(bintree.leftChild, bintree.id)),
63 | SqlObject("right", Join(bintree.rightChild, bintree.id)),
64 | )
65 | )
66 | )
67 |
68 | override val selectElaborator = SelectElaborator {
69 | case (QueryType, "bintree", List(Binding("id", IntValue(id)))) =>
70 | Elab.transformChild(child => Unique(Filter(Eql(BinTreeType / "id", Const(id)), child)))
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/modules/sql-core/src/test/scala/SqlUnionsMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sql.test
17 |
18 | import grackle._
19 | import grackle.syntax._
20 | import Predicate._
21 |
22 | trait SqlUnionsMapping[F[_]] extends SqlTestMapping[F] {
23 |
24 | object collections extends TableDef("collections") {
25 | val id = col("id", text)
26 | val itemType = col("item_type", text)
27 | val itemA = col("itema", text)
28 | val itemB = col("itemb", text)
29 | }
30 |
31 | val schema =
32 | schema"""
33 | type Query {
34 | collection: [Item!]!
35 | }
36 | type ItemA {
37 | itema: String!
38 | }
39 | type ItemB {
40 | itemb: String!
41 | }
42 | union Item = ItemA | ItemB
43 | """
44 |
45 | val QueryType = schema.ref("Query")
46 | val ItemAType = schema.ref("ItemA")
47 | val ItemBType = schema.ref("ItemB")
48 | val ItemType = schema.ref("Item")
49 |
50 | val typeMappings =
51 | List(
52 | ObjectMapping(
53 | tpe = QueryType,
54 | fieldMappings =
55 | List(
56 | SqlObject("collection")
57 | )
58 | ),
59 | SqlUnionMapping(
60 | tpe = ItemType,
61 | discriminator = itemTypeDiscriminator,
62 | fieldMappings =
63 | List(
64 | SqlField("id", collections.id, key = true, hidden = true),
65 | SqlField("itemType", collections.itemType, discriminator = true, hidden = true)
66 | )
67 | ),
68 | ObjectMapping(
69 | tpe = ItemAType,
70 | fieldMappings =
71 | List(
72 | SqlField("id", collections.id, key = true, hidden = true),
73 | SqlField("itema", collections.itemA)
74 | )
75 | ),
76 | ObjectMapping(
77 | tpe = ItemBType,
78 | fieldMappings =
79 | List(
80 | SqlField("id", collections.id, key = true, hidden = true),
81 | SqlField("itemb", collections.itemB)
82 | )
83 | )
84 | )
85 |
86 | object itemTypeDiscriminator extends SqlDiscriminator {
87 | def discriminate(c: Cursor): Result[Type] =
88 | for {
89 | it <- c.fieldAs[String]("itemType")
90 | } yield it match {
91 | case "ItemA" => ItemAType
92 | case "ItemB" => ItemBType
93 | }
94 |
95 | def narrowPredicate(subtpe: Type): Result[Predicate] = {
96 | def mkPredicate(tpe: String): Result[Predicate] =
97 | Eql(ItemType / "itemType", Const(tpe)).success
98 |
99 | subtpe match {
100 | case ItemAType => mkPredicate("ItemA")
101 | case ItemBType => mkPredicate("ItemB")
102 | case _ => Result.internalError(s"Invalid discriminator: $subtpe")
103 | }
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/modules/sql-pg/js-jvm/src/test/scala/SqlPgDatabaseSuite.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle.sqlpg
17 | package test
18 |
19 | import munit.CatsEffectSuite
20 |
21 | trait SqlPgDatabaseSuite extends CatsEffectSuite {
22 | case class PostgresConnectionInfo(host: String, port: Int) {
23 |
24 | val driverClassName = "org.postgresql.Driver"
25 | val databaseName = "test"
26 | val jdbcUrl = s"jdbc:postgresql://$host:$port/$databaseName"
27 | val username = "test"
28 | val password = "test"
29 | }
30 | object PostgresConnectionInfo {
31 | val DefaultPort = 5432
32 | }
33 |
34 | val postgresConnectionInfo: PostgresConnectionInfo =
35 | PostgresConnectionInfo("localhost", PostgresConnectionInfo.DefaultPort)
36 | }
37 |
--------------------------------------------------------------------------------
/modules/sql-pg/shared/src/main/scala/SqlPgMapping.scala:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
2 | // Copyright (c) 2016-2023 Grackle Contributors
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package grackle
17 | package sqlpg
18 |
19 | import cats.MonadThrow
20 | import cats.syntax.all._
21 |
22 | import grackle.sql.SqlMappingLike
23 | import Query.OrderSelection
24 |
25 | abstract class SqlPgMapping[F[_]](implicit val M: MonadThrow[F]) extends Mapping[F] with SqlPgMappingLike[F]
26 |
27 | trait SqlPgMappingLike[F[_]] extends SqlMappingLike[F] {
28 | import SqlQuery.SqlSelect
29 | import TableExpr.Laterality
30 |
31 | def collateToFragment: Fragment =
32 | Fragments.const(" COLLATE \"C\"")
33 |
34 | def aliasDefToFragment(alias: String): Fragment =
35 | Fragments.const(s" AS $alias")
36 |
37 | def offsetToFragment(offset: Fragment): Fragment =
38 | Fragments.const(" OFFSET ") |+| offset
39 |
40 | def limitToFragment(limit: Fragment): Fragment =
41 | Fragments.const(" LIMIT ") |+| limit
42 |
43 | def likeToFragment(expr: Fragment, pattern: String, caseInsensitive: Boolean): Fragment = {
44 | val op = if(caseInsensitive) "ILIKE" else "LIKE"
45 | expr |+| Fragments.const(s" $op ") |+| Fragments.bind(stringEncoder, pattern)
46 | }
47 |
48 | def ascribedNullToFragment(codec: Codec): Fragment =
49 | Fragments.sqlTypeName(codec) match {
50 | case Some(name) => Fragments.const(s"(NULL :: $name)")
51 | case None => Fragments.const("NULL")
52 | }
53 |
54 | def collateSelected: Boolean = true
55 |
56 | def distinctOnToFragment(dcols: List[Fragment]): Fragment =
57 | Fragments.const("DISTINCT ON ") |+| Fragments.parentheses(dcols.intercalate(Fragments.const(", ")))
58 |
59 | def distinctOrderColumn(owner: ColumnOwner, col: SqlColumn, predCols: List[SqlColumn], orders: List[OrderSelection[_]]): SqlColumn = col
60 |
61 | def encapsulateUnionBranch(s: SqlSelect): SqlSelect = s
62 | def mkLateral(inner: Boolean): Laterality = Laterality.Lateral
63 | def defaultOffsetForSubquery(subquery: SqlQuery): SqlQuery = subquery
64 | def defaultOffsetForLimit(limit: Option[Int]): Option[Int] = None
65 |
66 | def orderToFragment(col: Fragment, ascending: Boolean, nullsLast: Boolean): Fragment = {
67 | val dir = if(ascending) Fragments.empty else Fragments.const(" DESC")
68 | val nulls =
69 | if(!nullsLast && ascending)
70 | Fragments.const(" NULLS FIRST ")
71 | else if(nullsLast && !ascending)
72 | Fragments.const(" NULLS LAST ")
73 | else
74 | Fragments.empty
75 |
76 | col |+| dir |+| nulls
77 | }
78 |
79 | def nullsHigh: Boolean = true
80 | }
81 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.11.1
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.8.0")
2 | addSbtPlugin("org.typelevel" % "sbt-typelevel-site" % "0.8.0")
3 | addSbtPlugin("io.spray" % "sbt-revolver" % "0.10.0")
4 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4")
5 | addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0")
6 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.7")
7 | addSbtPlugin("nl.zolotko.sbt" % "sbt-jfr" % "0.0.1")
8 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1")
9 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.18.2")
10 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17")
11 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.3.1")
12 |
--------------------------------------------------------------------------------
/project/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.3")
2 |
--------------------------------------------------------------------------------
/run-local.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | docker compose up -d --wait
4 |
--------------------------------------------------------------------------------
/scripts/mssql-query.sh:
--------------------------------------------------------------------------------
1 | sqlcmd -S localhost -U sa -P Test_123_Test -d test -Y 10 -y 10 -i $*
2 |
--------------------------------------------------------------------------------
/scripts/oracle-opts.sql:
--------------------------------------------------------------------------------
1 | show errors
2 | set markup csv on
3 |
--------------------------------------------------------------------------------
/scripts/oracle-query.sh:
--------------------------------------------------------------------------------
1 | rlwrap sqlplus test/test@//localhost/FREEPDB1
2 |
3 |
--------------------------------------------------------------------------------
/scripts/pg-query.sh:
--------------------------------------------------------------------------------
1 | PGPASSWORD=test psql -h localhost -U test -d test $*
2 |
--------------------------------------------------------------------------------
/stop-local.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | docker compose stop
4 |
--------------------------------------------------------------------------------
/testdata/mssql/array-join.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE array_join_root (
2 | id VARCHAR(100) PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE array_join_list_a (
6 | id VARCHAR(100) PRIMARY KEY,
7 | root_id VARCHAR(100),
8 | a_elem VARCHAR(100) CHECK (ISJSON(a_elem) = 1)
9 | );
10 |
11 | CREATE TABLE array_join_list_b (
12 | id VARCHAR(100) PRIMARY KEY,
13 | root_id VARCHAR(100),
14 | b_elem INTEGER
15 | );
16 |
17 | INSERT INTO array_join_root (id) VALUES
18 | ('r0'),
19 | ('r1');
20 |
21 | INSERT INTO array_join_list_a (id, root_id, a_elem) VALUES
22 | ('a0', 'r0', '["foo1", "foo2"]'),
23 | ('a1', 'r0', '["bar1", "bar2"]'),
24 | ('a2', 'r0', '["baz1", "baz2"]'),
25 | ('a3', 'r0', '["quux1", "quux2"]'),
26 | ('a4', 'r1', '["foo11", "foo22"]'),
27 | ('a5', 'r1', '["bar11", "bar22"]'),
28 | ('a6', 'r1', '["baz11", "baz22"]'),
29 | ('a7', 'r1', '["quux11", "quux22"]');
30 |
31 | INSERT INTO array_join_list_b (id, root_id, b_elem) VALUES
32 | ('b0', 'r0', '23'),
33 | ('b1', 'r0', '13'),
34 | ('b2', 'r0', '17'),
35 | ('b3', 'r0', '11'),
36 | ('b4', 'r1', '231'),
37 | ('b5', 'r1', '131'),
38 | ('b6', 'r1', '171'),
39 | ('b7', 'r1', '111');
40 |
41 | GO
42 |
--------------------------------------------------------------------------------
/testdata/mssql/coalesce.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE r (
2 | id VARCHAR(100) PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE ca (
6 | id VARCHAR(100) PRIMARY KEY,
7 | rid VARCHAR(100) NOT NULL,
8 | a INTEGER NOT NULL
9 | );
10 |
11 | CREATE TABLE cb (
12 | id VARCHAR(100) PRIMARY KEY,
13 | rid VARCHAR(100) NOT NULL,
14 | b BIT NOT NULL
15 | );
16 |
17 | CREATE TABLE cc (
18 | id VARCHAR(100) PRIMARY KEY,
19 | rid VARCHAR(100) NOT NULL,
20 | c DATETIMEOFFSET(7) NOT NULL
21 | );
22 |
23 | INSERT INTO r (id) VALUES
24 | ('R1'),
25 | ('R2'),
26 | ('R3');
27 |
28 | INSERT INTO ca (id, rid, a) VALUES
29 | ('CA1a', 'R1', '10'),
30 | ('CA1b', 'R1', '11'),
31 | ('CA2', 'R2', '20'),
32 | ('CA3', 'R3', '30');
33 |
34 | INSERT INTO cb (id, rid, b) VALUES
35 | ('CB2a', 'R2', 1),
36 | ('CB2b', 'R2', 0),
37 | ('CB3', 'R3', 1);
38 |
39 | INSERT INTO cc (id, rid, c) VALUES
40 | ('CC1', 'R1', '2020-05-27 21:00:00 +02:00'),
41 | ('CC2', 'R2', '2004-10-19 10:23:54 +02:00'),
42 | ('CC3', 'R3', '2014-10-19 10:23:54 +02:00');
43 |
44 | GO
45 |
--------------------------------------------------------------------------------
/testdata/mssql/composite-keys.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE composite_key_parent (
2 | key_1 INTEGER NOT NULL,
3 | key_2 VARCHAR(100) NOT NULL,
4 | PRIMARY KEY (key_1, key_2)
5 | );
6 |
7 | CREATE TABLE composite_key_child (
8 | id INTEGER PRIMARY KEY,
9 | parent_1 INTEGER NOT NULL,
10 | parent_2 VARCHAR(100) NOT NULL,
11 | FOREIGN KEY (parent_1, parent_2) REFERENCES composite_key_parent (key_1, key_2)
12 | );
13 |
14 | INSERT INTO composite_key_parent (key_1, key_2) VALUES
15 | (1, 'foo'),
16 | (1, 'bar'),
17 | (2, 'foo'),
18 | (2, 'bar');
19 |
20 | INSERT INTO composite_key_child (id, parent_1, parent_2) VALUES
21 | (1, 1, 'foo'),
22 | (2, 1, 'bar'),
23 | (3, 2, 'foo'),
24 | (4, 2, 'bar');
25 |
26 | GO
27 |
--------------------------------------------------------------------------------
/testdata/mssql/cursor-json.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE brands (
2 | id Int PRIMARY KEY,
3 | categories Int
4 | )
5 |
6 | INSERT INTO brands (id, categories) VALUES
7 | ('1', '8'),
8 | ('2', '16')
9 |
10 | GO
11 |
--------------------------------------------------------------------------------
/testdata/mssql/embedding.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE films (
2 | title VARCHAR(100) PRIMARY KEY,
3 | synopsis_short VARCHAR(100),
4 | synopsis_long VARCHAR(100)
5 | );
6 |
7 | CREATE TABLE series (
8 | title VARCHAR(100) PRIMARY KEY,
9 | synopsis_short VARCHAR(100),
10 | synopsis_long VARCHAR(100)
11 | );
12 |
13 | CREATE TABLE episodes2 (
14 | title VARCHAR(100) PRIMARY KEY,
15 | series_title VARCHAR(100) NOT NULL,
16 | synopsis_short VARCHAR(100),
17 | synopsis_long VARCHAR(100)
18 | );
19 |
20 | INSERT INTO films (title, synopsis_short, synopsis_long) VALUES
21 | ('Film 1', 'Short film 1', 'Long film 1'),
22 | ('Film 2', 'Short film 2', 'Long film 2'),
23 | ('Film 3', 'Short film 3', 'Long film 3');
24 |
25 | INSERT INTO series (title, synopsis_short, synopsis_long) VALUES
26 | ('Series 1', 'Short series 1', 'Long series 1'),
27 | ('Series 2', 'Short series 2', 'Long series 2'),
28 | ('Series 3', 'Short series 3', 'Long series 3');
29 |
30 | INSERT INTO episodes2 (title, series_title, synopsis_short, synopsis_long) VALUES
31 | ('S01E01', 'Series 1', 'Short S01E01', 'Long S01E01'),
32 | ('S01E02', 'Series 1', 'Short S01E02', 'Long S01E02'),
33 | ('S01E03', 'Series 1', 'Short S01E03', 'Long S01E03'),
34 | ('S02E01', 'Series 2', 'Short S02E01', 'Long S02E01'),
35 | ('S02E02', 'Series 2', 'Short S02E02', 'Long S02E02'),
36 | ('S02E03', 'Series 2', 'Short S02E03', 'Long S02E03'),
37 | ('S03E01', 'Series 3', 'Short S03E01', 'Long S03E01'),
38 | ('S03E02', 'Series 3', 'Short S03E02', 'Long S03E02'),
39 | ('S03E03', 'Series 3', 'Short S03E03', 'Long S03E03');
40 |
41 | GO
42 |
--------------------------------------------------------------------------------
/testdata/mssql/embedding2.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE t_program (
2 | c_program_id VARCHAR(100) NOT NULL PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE t_observation (
6 | c_program_id VARCHAR(100) NOT NULL REFERENCES t_program(c_program_id),
7 | c_observation_id VARCHAR(100) NOT NULL PRIMARY KEY
8 | );
9 |
10 | INSERT INTO t_program (c_program_id) VALUES
11 | ('foo'),
12 | ('bar');
13 |
14 | INSERT INTO t_observation (c_program_id, c_observation_id) VALUES
15 | ('foo', 'fo1'),
16 | ('foo', 'fo2'),
17 | ('bar', 'bo1'),
18 | ('bar', 'bo2');
19 |
20 | GO
21 |
--------------------------------------------------------------------------------
/testdata/mssql/filter-join-alias.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE episodes3 (
2 | id VARCHAR(100),
3 | name VARCHAR(100),
4 | PRIMARY KEY (id, name)
5 | );
6 |
7 | CREATE TABLE images3 (
8 | public_url VARCHAR(100) PRIMARY KEY,
9 | id VARCHAR(100) NOT NULL,
10 | name VARCHAR(100) NOT NULL
11 | );
12 |
13 | INSERT INTO episodes3 (id, name) VALUES ('a', 'abc');
14 |
15 | INSERT INTO images3 (public_url, id, name) VALUES ('test', 'a', 'abc');
16 |
17 | GO
18 |
--------------------------------------------------------------------------------
/testdata/mssql/filter-order-offset-limit-2.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE root_2 (
2 | id VARCHAR(100) PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE containers_2 (
6 | id VARCHAR(100) PRIMARY KEY,
7 | root_id VARCHAR(100)
8 | );
9 |
10 | CREATE TABLE lista_2 (
11 | id VARCHAR(100) PRIMARY KEY,
12 | container_id VARCHAR(100)
13 | );
14 |
15 | CREATE TABLE listb_2 (
16 | id VARCHAR(100) PRIMARY KEY,
17 | container_id VARCHAR(100)
18 | );
19 |
20 | INSERT INTO root_2 (id) VALUES
21 | ('r0'),
22 | ('r1');
23 |
24 | INSERT INTO containers_2 (id, root_id) VALUES
25 | ('c0', 'r0'),
26 | ('c1', 'r0'),
27 | ('c2', 'r1'),
28 | ('c3', 'r1');
29 |
30 | INSERT INTO lista_2 (id, container_id) VALUES
31 | ('a00', 'c0'),
32 | ('a01', 'c0'),
33 | ('a10', 'c1'),
34 | ('a11', 'c1'),
35 | ('a20', 'c2'),
36 | ('a21', 'c2'),
37 | ('a30', 'c3'),
38 | ('a31', 'c3');
39 |
40 | INSERT INTO listb_2 (id, container_id) VALUES
41 | ('b00', 'c0'),
42 | ('b01', 'c0'),
43 | ('b10', 'c1'),
44 | ('b11', 'c1'),
45 | ('b20', 'c2'),
46 | ('b21', 'c2'),
47 | ('b30', 'c3'),
48 | ('b31', 'c3');
49 |
50 | GO
51 |
--------------------------------------------------------------------------------
/testdata/mssql/filter-order-offset-limit.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE root (
2 | id VARCHAR(100) PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE lista (
6 | id VARCHAR(100) PRIMARY KEY,
7 | root_id VARCHAR(100),
8 | a_elem VARCHAR(100)
9 | );
10 |
11 | CREATE TABLE listb (
12 | id VARCHAR(100) PRIMARY KEY,
13 | root_id VARCHAR(100),
14 | b_elem INTEGER
15 | );
16 |
17 | INSERT INTO root (id) VALUES
18 | ('r0'),
19 | ('r1');
20 |
21 | INSERT INTO lista (id, root_id, a_elem) VALUES
22 | ('a0', 'r0', 'foo'),
23 | ('a1', 'r0', 'bar'),
24 | ('a2', 'r0', 'baz'),
25 | ('a3', 'r0', 'quux'),
26 | ('a4', 'r1', 'foo1'),
27 | ('a5', 'r1', 'bar1'),
28 | ('a6', 'r1', 'baz1'),
29 | ('a7', 'r1', 'quux1');
30 |
31 | INSERT INTO listb (id, root_id, b_elem) VALUES
32 | ('b0', 'r0', 23),
33 | ('b1', 'r0', 13),
34 | ('b2', 'r0', 17),
35 | ('b3', 'r0', 11),
36 | ('b4', 'r1', 231),
37 | ('b5', 'r1', 131),
38 | ('b6', 'r1', 171),
39 | ('b7', 'r1', 111);
40 |
41 | GO
42 |
--------------------------------------------------------------------------------
/testdata/mssql/graph.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE graph_node (
2 | id INTEGER PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE graph_edge (
6 | id INTEGER PRIMARY KEY,
7 | a INTEGER,
8 | b INTEGER
9 | );
10 |
11 | INSERT INTO graph_node (id) VALUES
12 | ('1'),
13 | ('2'),
14 | ('3'),
15 | ('4');
16 |
17 | INSERT INTO graph_edge (id, a, b) VALUES
18 | ('12', '1', '2'),
19 | ('13', '1', '3'),
20 | ('24', '2', '4'),
21 | ('34', '3', '4');
22 |
23 | GO
24 |
--------------------------------------------------------------------------------
/testdata/mssql/interfaces.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE entities (
2 | id VARCHAR(100) PRIMARY KEY,
3 | entity_type INTEGER NOT NULL,
4 | title VARCHAR(100),
5 | synopsis_short VARCHAR(100),
6 | synopsis_long VARCHAR(100),
7 | film_rating VARCHAR(100),
8 | film_label INTEGER,
9 | series_number_of_episodes INTEGER,
10 | series_label VARCHAR(100),
11 | image_url VARCHAR(100),
12 | hidden_image_url VARCHAR(100)
13 | );
14 |
15 | CREATE TABLE episodes (
16 | id VARCHAR(100) PRIMARY KEY,
17 | series_id VARCHAR(100) NOT NULL,
18 | title VARCHAR(100),
19 | synopsis_short VARCHAR(100),
20 | synopsis_long VARCHAR(100)
21 | );
22 |
23 | INSERT INTO entities (id, entity_type, title, synopsis_short, synopsis_long, film_rating, film_label, series_number_of_episodes, series_label, image_url, hidden_image_url) VALUES
24 | ('1', 1, 'Film 1', 'Short film 1', 'Long film 1', 'PG', 1, NULL, NULL, 'http://www.example.com/film1.jpg', NULL),
25 | ('2', 1, 'Film 2', 'Short film 2', 'Long film 2', 'U', 2, NULL, NULL, 'http://www.example.com/film2.jpg', NULL),
26 | ('3', 1, 'Film 3', 'Short film 3', 'Long film 3', '15', 3, NULL, NULL, 'http://www.example.com/film3.jpg', NULL),
27 | ('4', 2, 'Series 1', 'Short series 1', 'Long series 1', NULL, NULL, 5, 'One', NULL, 'hidden_series1.jpg'),
28 | ('5', 2, 'Series 2', 'Short series 2', 'Long series 2', NULL, NULL, 6, 'Two', NULL, 'hidden_series2.jpg'),
29 | ('6', 2, 'Series 3', 'Short series 3', 'Long series 3', NULL, NULL, 7, 'Three', NULL, 'hidden_series3.jpg');
30 |
31 | INSERT INTO episodes (id, series_id, title, synopsis_short, synopsis_long) VALUES
32 | ('1', '4', 'S1E1', 'Short S1E1', 'Long S1E1'),
33 | ('2', '4', 'S1E2', 'Short S1E2', 'Long S1E2'),
34 | ('3', '5', 'S2E1', 'Short S2E1', 'Long S2E1'),
35 | ('4', '5', 'S2E2', 'Short S2E2', 'Long S2E2'),
36 | ('5', '6', 'S3E1', 'Short S3E1', 'Long S3E1'),
37 | ('6', '6', 'S3E2', 'Short S3E2', 'Long S3E2');
38 |
39 | GO
40 |
--------------------------------------------------------------------------------
/testdata/mssql/jsonb.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE records (
2 | id Integer PRIMARY KEY,
3 | record NVARCHAR(500) CHECK (ISJSON(record) = 1)
4 | );
5 |
6 | INSERT INTO records (id, record) VALUES
7 | ('1', '{"bool":true,"int":1,"float":1.3,"string":"foo","id":"Foo","array":[1,2,3],"choice":"ONE","object":{"id":"obj0","aField":27},"children":[{"id":"a0","aField":11},{"id":"b0","bField":"wibble"}]}'),
8 | ('2', '{"bool":false,"int":2,"float":2.4,"string":"bar","id":"Bar","array":[4,5,6],"choice":"TWO","object":{"id":"obj1","aField":28},"children":[{"id":"a1","aField":12},{"id":"b1","bField":"wobble"}]}'),
9 | ('3', '{"bool":true,"int":3,"float":3.5,"string":"baz","id":"Baz","array":[7,8,9],"choice":"THREE","object":{"id":"obj2","aField":29},"children":[{"id":"a2","aField":13},{"id":"b2","bField":"quux"}]}');
10 |
11 | GO
12 |
--------------------------------------------------------------------------------
/testdata/mssql/like.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE likes (
2 | id INTEGER PRIMARY KEY,
3 | notnullable VARCHAR(100) NOT NULL,
4 | nullable VARCHAR(100)
5 | );
6 |
7 | INSERT INTO likes (id, notnullable, nullable) VALUES
8 | (1, 'foo', NULL),
9 | (2, 'bar', 'baz');
10 |
11 | GO
12 |
--------------------------------------------------------------------------------
/testdata/mssql/movies.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE IF EXISTS movies;
2 |
3 | CREATE TABLE movies (
4 | id VARCHAR(36) PRIMARY KEY,
5 | title VARCHAR(100) NOT NULL,
6 | genre INTEGER NOT NULL,
7 | releasedate DATE NOT NULL,
8 | showtime TIME NOT NULL,
9 | nextshowing DATETIMEOFFSET(7) NOT NULL,
10 | duration INTEGER NOT NULL,
11 | categories VARCHAR(100) CHECK (ISJSON(categories) = 1) NOT NULL,
12 | features VARCHAR(100) CHECK (ISJSON(features) = 1) NOT NULL,
13 | tags INTEGER NOT NULL
14 | );
15 |
16 | INSERT INTO movies (id, title, genre, releasedate, showtime, nextshowing, duration, categories, features, tags) VALUES
17 | ('6a7837fc-b463-4d32-b628-0f4b3065cb21', 'Celine et Julie Vont en Bateau', '1', '1974-10-07', '19:35:00', '2020-05-22 19:35:00 +00:00', 12300000, '["drama","comedy"]', '["hd","hls"]', '1');
18 |
19 | INSERT INTO movies (id, title, genre, releasedate, showtime, nextshowing, duration, categories, features, tags) VALUES
20 | ('11daf8c0-11c3-4453-bfe1-cb6e6e2f9115', 'Duelle', '1', '1975-09-15', '19:20:00', '2020-05-27 19:20:00 +00:00', 7260000, '["drama"]', '["hd"]', '1');
21 |
22 | INSERT INTO movies (id, title, genre, releasedate, showtime, nextshowing, duration, categories, features, tags) VALUES
23 | ('aea9756f-621b-42d5-b130-71f3916c4ba3', 'L''Amour fou', '1', '1969-01-15', '21:00:00', '2020-05-27 21:00:00 +00:00', 15120000, '["drama"]', '["hd"]', '2');
24 |
25 | INSERT INTO movies (id, title, genre, releasedate, showtime, nextshowing, duration, categories, features, tags) VALUES
26 | ('2ddb041f-86c2-4bd3-848c-990a3862634e', 'Last Year at Marienbad', '1', '1961-06-25', '20:30:00', '2020-05-26 20:30:00 +00:00', 5640000, '["drama"]', '["hd"]', '5');
27 |
28 | INSERT INTO movies (id, title, genre, releasedate, showtime, nextshowing, duration, categories, features, tags) VALUES
29 | ('8ae5b13b-044c-4ff0-8b71-ccdb7d77cd88', 'Zazie dans le Métro', '3', '1960-10-28', '20:15:00', '2020-05-25 20:15:00 +00:00', 5340000, '["drama", "comedy"]', '["hd"]', '3');
30 |
31 | INSERT INTO movies (id, title, genre, releasedate, showtime, nextshowing, duration, categories, features, tags) VALUES
32 | ('9dce9deb-9188-4cc2-9685-9842b8abdd34', 'Alphaville', '2', '1965-05-05', '19:45:00', '2020-05-19 19:45:00 +00:00', 5940000, '["drama", "science fiction"]', '["hd"]', '4');
33 |
34 | INSERT INTO movies (id, title, genre, releasedate, showtime, nextshowing, duration, categories, features, tags) VALUES
35 | ('1bf00ac6-91ab-4e51-b686-3fd5e2324077', 'Stalker', '1', '1979-05-13', '15:30:00', '2020-05-19 15:30:00 +00:00', 9660000, '["drama", "science fiction"]', '["hd"]', '7');
36 |
37 | INSERT INTO movies (id, title, genre, releasedate, showtime, nextshowing, duration, categories, features, tags) VALUES
38 | ('6a878e06-6563-4a0c-acd9-d28dcfb2e91a', 'Weekend', '3', '1967-12-29', '22:30:00', '2020-05-19 22:30:00 +00:00', 6300000, '["drama", "comedy"]', '["hd"]', '2');
39 |
40 | INSERT INTO movies (id, title, genre, releasedate, showtime, nextshowing, duration, categories, features, tags) VALUES
41 | ('2a40415c-ea6a-413f-bbef-a80ae280c4ff', 'Daisies', '3', '1966-12-30', '21:30:00', '2020-05-15 21:30:00 +00:00', 4560000, '["drama", "comedy"]', '["hd"]', '6');
42 |
43 | INSERT INTO movies (id, title, genre, releasedate, showtime, nextshowing, duration, categories, features, tags) VALUES
44 | ('2f6dcb0a-4122-4a21-a1c6-534744dd6b85', 'Le Pont du Nord', '1', '1982-01-13', '20:45:00', '2020-05-11 20:45:00 +00:00', 7620000, '["drama"]', '["hd"]', '3');
45 |
46 | GO
47 |
--------------------------------------------------------------------------------
/testdata/mssql/mutation.sql:
--------------------------------------------------------------------------------
1 | -- A sequence to test out inserting into a table.
2 | CREATE SEQUENCE city_id START WITH 5000;
3 |
4 | GO
5 |
--------------------------------------------------------------------------------
/testdata/mssql/projection.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE level0 (
2 | id VARCHAR(100) PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE level1 (
6 | id VARCHAR(100) PRIMARY KEY,
7 | level0_id VARCHAR(100)
8 | );
9 |
10 | CREATE TABLE level2 (
11 | id VARCHAR(100) PRIMARY KEY,
12 | level1_id VARCHAR(100),
13 | attr BIT
14 | );
15 |
16 | INSERT INTO level0 (id) VALUES
17 | ('00'),
18 | ('01');
19 |
20 | INSERT INTO level1 (id, level0_id) VALUES
21 | ('10', '00'),
22 | ('11', '00'),
23 | ('12', '01'),
24 | ('13', '01');
25 |
26 | INSERT INTO level2 (id, level1_id, attr) VALUES
27 | ('20', '10', 0),
28 | ('21', '10', 0),
29 | ('22', '11', 0),
30 | ('23', '11', 0),
31 | ('24', '12', 1),
32 | ('25', '12', 0),
33 | ('26', '13', 0),
34 | ('27', '13', 0);
35 |
36 | GO
37 |
--------------------------------------------------------------------------------
/testdata/mssql/recursive-interfaces.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE recursive_interface_items (
2 | id VARCHAR(100) PRIMARY KEY,
3 | item_type INTEGER NOT NULL
4 | );
5 |
6 | CREATE TABLE recursive_interface_next_items (
7 | id VARCHAR(100) PRIMARY KEY,
8 | next_item VARCHAR(100)
9 | );
10 |
11 | INSERT INTO recursive_interface_items (id, item_type) VALUES
12 | ('1', '1'),
13 | ('2', '2');
14 |
15 | INSERT INTO recursive_interface_next_items (id, next_item) VALUES
16 | ('1', '2'),
17 | ('2', '1');
18 |
19 | GO
20 |
--------------------------------------------------------------------------------
/testdata/mssql/sibling-lists.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE seq_scan_a
2 | (
3 | id VARCHAR(100) PRIMARY KEY
4 | );
5 |
6 | CREATE TABLE seq_scan_b
7 | (
8 | id VARCHAR(100) PRIMARY KEY,
9 | a_id VARCHAR(100) NOT NULL
10 | );
11 |
12 | CREATE INDEX seq_scan_b_a_id_idx ON seq_scan_b(a_id);
13 |
14 | CREATE TABLE seq_scan_c
15 | (
16 | id VARCHAR(100) PRIMARY KEY,
17 | b_id VARCHAR(100) NOT NULL,
18 | name_c VARCHAR(100) NOT NULL
19 | );
20 |
21 | CREATE INDEX seq_scan_c_b_id_idx ON seq_scan_c(b_id);
22 |
23 | CREATE TABLE seq_scan_d
24 | (
25 | id VARCHAR(100) PRIMARY KEY,
26 | b_id VARCHAR(100) NOT NULL,
27 | name_d VARCHAR(100) NOT NULL
28 | );
29 |
30 | CREATE INDEX seq_scan_d_b_id_idx ON seq_scan_d(b_id);
31 |
32 | INSERT INTO seq_scan_a(id) VALUES('id_a_1');
33 |
34 | INSERT INTO seq_scan_b(id, a_id) VALUES('id_b_1', 'id_a_1');
35 |
36 | INSERT INTO seq_scan_c(id, b_id, name_c) VALUES('id_c_1', 'id_b_1', 'name_c');
37 | INSERT INTO seq_scan_d(id, b_id, name_d) VALUES('id_d_1', 'id_b_1', 'name_d');
38 |
39 | GO
40 |
--------------------------------------------------------------------------------
/testdata/mssql/tree.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE bintree (
2 | id INTEGER PRIMARY KEY,
3 | left_child INTEGER,
4 | right_child INTEGER
5 | );
6 |
7 | INSERT INTO bintree (id, left_child, right_child) VALUES
8 | (0, 1, 2),
9 | (1, 3, 4),
10 | (2, 5, 6),
11 | (3, NULL, NULL),
12 | (4, NULL, NULL),
13 | (5, NULL, NULL),
14 | (6, NULL, NULL);
15 |
16 | GO
17 |
--------------------------------------------------------------------------------
/testdata/mssql/unions.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE collections (
2 | id VARCHAR(100) PRIMARY KEY,
3 | item_type VARCHAR(100) NOT NULL,
4 | itema VARCHAR(100),
5 | itemb VARCHAR(100)
6 | );
7 |
8 | INSERT INTO collections (id, item_type, itema, itemb) VALUES
9 | ('1', 'ItemA', 'A', NULL),
10 | ('2', 'ItemB', NULL, 'B');
11 |
12 | GO
13 |
--------------------------------------------------------------------------------
/testdata/oracle/array-join.sql:
--------------------------------------------------------------------------------
1 | CREATE TYPE string_array AS VARRAY(100) OF VARCHAR2(100);
2 | /
3 |
4 | CREATE TABLE array_join_root (
5 | id VARCHAR2(100) PRIMARY KEY
6 | );
7 |
8 | CREATE TABLE array_join_list_a (
9 | id VARCHAR2(100) PRIMARY KEY,
10 | root_id VARCHAR2(100),
11 | a_elem string_array
12 | );
13 |
14 | CREATE TABLE array_join_list_b (
15 | id VARCHAR2(100) PRIMARY KEY,
16 | root_id VARCHAR2(100),
17 | b_elem INTEGER
18 | );
19 |
20 | INSERT INTO array_join_root (id) VALUES
21 | ('r0'),
22 | ('r1');
23 |
24 | INSERT INTO array_join_list_a (id, root_id, a_elem) VALUES
25 | ('a0', 'r0', string_array('foo1', 'foo2')),
26 | ('a1', 'r0', string_array('bar1', 'bar2')),
27 | ('a2', 'r0', string_array('baz1', 'baz2')),
28 | ('a3', 'r0', string_array('quux1', 'quux2')),
29 | ('a4', 'r1', string_array('foo11', 'foo22')),
30 | ('a5', 'r1', string_array('bar11', 'bar22')),
31 | ('a6', 'r1', string_array('baz11', 'baz22')),
32 | ('a7', 'r1', string_array('quux11', 'quux22'));
33 |
34 |
35 | INSERT INTO array_join_list_b (id, root_id, b_elem) VALUES
36 | ('b0', 'r0', '23'),
37 | ('b1', 'r0', '13'),
38 | ('b2', 'r0', '17'),
39 | ('b3', 'r0', '11'),
40 | ('b4', 'r1', '231'),
41 | ('b5', 'r1', '131'),
42 | ('b6', 'r1', '171'),
43 | ('b7', 'r1', '111');
44 |
--------------------------------------------------------------------------------
/testdata/oracle/coalesce.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE r (
2 | id VARCHAR(100) PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE ca (
6 | id VARCHAR(100) PRIMARY KEY,
7 | rid VARCHAR(100) NOT NULL,
8 | a INTEGER NOT NULL
9 | );
10 |
11 | CREATE TABLE cb (
12 | id VARCHAR(100) PRIMARY KEY,
13 | rid VARCHAR(100) NOT NULL,
14 | b BOOLEAN NOT NULL
15 | );
16 |
17 | CREATE TABLE cc (
18 | id VARCHAR(100) PRIMARY KEY,
19 | rid VARCHAR(100) NOT NULL,
20 | c TIMESTAMP WITH TIME ZONE NOT NULL
21 | );
22 |
23 | INSERT INTO r (id) VALUES
24 | ('R1'),
25 | ('R2'),
26 | ('R3');
27 |
28 | INSERT INTO ca (id, rid, a) VALUES
29 | ('CA1a', 'R1', '10'),
30 | ('CA1b', 'R1', '11'),
31 | ('CA2', 'R2', '20'),
32 | ('CA3', 'R3', '30');
33 |
34 | INSERT INTO cb (id, rid, b) VALUES
35 | ('CB2a', 'R2', 'TRUE'),
36 | ('CB2b', 'R2', 'FALSE'),
37 | ('CB3', 'R3', 'TRUE');
38 |
39 | INSERT INTO cc (id, rid, c) VALUES
40 | ('CC1', 'R1', TIMESTAMP '2020-05-27 21:00:00 +02:00'),
41 | ('CC2', 'R2', TIMESTAMP '2004-10-19 10:23:54 +02:00'),
42 | ('CC3', 'R3', TIMESTAMP '2014-10-19 10:23:54 +02:00');
43 |
--------------------------------------------------------------------------------
/testdata/oracle/composite-keys.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE composite_key_parent (
2 | key_1 INTEGER NOT NULL,
3 | key_2 VARCHAR(100) NOT NULL,
4 | PRIMARY KEY (key_1, key_2)
5 | );
6 |
7 | CREATE TABLE composite_key_child (
8 | id INTEGER PRIMARY KEY,
9 | parent_1 INTEGER NOT NULL,
10 | parent_2 VARCHAR(100) NOT NULL,
11 | FOREIGN KEY (parent_1, parent_2) REFERENCES composite_key_parent (key_1, key_2)
12 | );
13 |
14 | INSERT INTO composite_key_parent (key_1, key_2) VALUES
15 | (1, 'foo'),
16 | (1, 'bar'),
17 | (2, 'foo'),
18 | (2, 'bar');
19 |
20 | INSERT INTO composite_key_child (id, parent_1, parent_2) VALUES
21 | (1, 1, 'foo'),
22 | (2, 1, 'bar'),
23 | (3, 2, 'foo'),
24 | (4, 2, 'bar');
25 |
--------------------------------------------------------------------------------
/testdata/oracle/cursor-json.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE brands (
2 | id Int PRIMARY KEY,
3 | categories Int
4 | );
5 |
6 | INSERT INTO brands (id, categories) VALUES
7 | ('1', '8'),
8 | ('2', '16');
9 |
10 |
--------------------------------------------------------------------------------
/testdata/oracle/embedding.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE films (
2 | title VARCHAR(100) PRIMARY KEY,
3 | synopsis_short VARCHAR(100),
4 | synopsis_long VARCHAR(100)
5 | );
6 |
7 | CREATE TABLE series (
8 | title VARCHAR(100) PRIMARY KEY,
9 | synopsis_short VARCHAR(100),
10 | synopsis_long VARCHAR(100)
11 | );
12 |
13 | CREATE TABLE episodes2 (
14 | title VARCHAR(100) PRIMARY KEY,
15 | series_title VARCHAR(100) NOT NULL,
16 | synopsis_short VARCHAR(100),
17 | synopsis_long VARCHAR(100)
18 | );
19 |
20 | INSERT INTO films (title, synopsis_short, synopsis_long) VALUES
21 | ('Film 1', 'Short film 1', 'Long film 1'),
22 | ('Film 2', 'Short film 2', 'Long film 2'),
23 | ('Film 3', 'Short film 3', 'Long film 3');
24 |
25 | INSERT INTO series (title, synopsis_short, synopsis_long) VALUES
26 | ('Series 1', 'Short series 1', 'Long series 1'),
27 | ('Series 2', 'Short series 2', 'Long series 2'),
28 | ('Series 3', 'Short series 3', 'Long series 3');
29 |
30 | INSERT INTO episodes2 (title, series_title, synopsis_short, synopsis_long) VALUES
31 | ('S01E01', 'Series 1', 'Short S01E01', 'Long S01E01'),
32 | ('S01E02', 'Series 1', 'Short S01E02', 'Long S01E02'),
33 | ('S01E03', 'Series 1', 'Short S01E03', 'Long S01E03'),
34 | ('S02E01', 'Series 2', 'Short S02E01', 'Long S02E01'),
35 | ('S02E02', 'Series 2', 'Short S02E02', 'Long S02E02'),
36 | ('S02E03', 'Series 2', 'Short S02E03', 'Long S02E03'),
37 | ('S03E01', 'Series 3', 'Short S03E01', 'Long S03E01'),
38 | ('S03E02', 'Series 3', 'Short S03E02', 'Long S03E02'),
39 | ('S03E03', 'Series 3', 'Short S03E03', 'Long S03E03');
40 |
--------------------------------------------------------------------------------
/testdata/oracle/embedding2.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE t_program (
2 | c_program_id VARCHAR(100) NOT NULL PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE t_observation (
6 | c_program_id VARCHAR(100) NOT NULL REFERENCES t_program(c_program_id),
7 | c_observation_id VARCHAR(100) NOT NULL PRIMARY KEY
8 | );
9 |
10 | INSERT INTO t_program (c_program_id) VALUES
11 | ('foo'),
12 | ('bar');
13 |
14 | INSERT INTO t_observation (c_program_id, c_observation_id) VALUES
15 | ('foo', 'fo1'),
16 | ('foo', 'fo2'),
17 | ('bar', 'bo1'),
18 | ('bar', 'bo2');
19 |
--------------------------------------------------------------------------------
/testdata/oracle/filter-join-alias.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE episodes3 (
2 | id VARCHAR(100),
3 | name VARCHAR(100),
4 | PRIMARY KEY (id, name)
5 | );
6 |
7 | CREATE TABLE images3 (
8 | public_url VARCHAR(100) PRIMARY KEY,
9 | id VARCHAR(100) NOT NULL,
10 | name VARCHAR(100) NOT NULL
11 | );
12 |
13 | INSERT INTO episodes3 (id, name) VALUES ('a', 'abc');
14 |
15 | INSERT INTO images3 (public_url, id, name) VALUES ('test', 'a', 'abc');
16 |
--------------------------------------------------------------------------------
/testdata/oracle/filter-order-offset-limit-2.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE root_2 (
2 | id VARCHAR(100) PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE containers_2 (
6 | id VARCHAR(100) PRIMARY KEY,
7 | root_id VARCHAR(100)
8 | );
9 |
10 | CREATE TABLE lista_2 (
11 | id VARCHAR(100) PRIMARY KEY,
12 | container_id VARCHAR(100)
13 | );
14 |
15 | CREATE TABLE listb_2 (
16 | id VARCHAR(100) PRIMARY KEY,
17 | container_id VARCHAR(100)
18 | );
19 |
20 | INSERT INTO root_2 (id) VALUES
21 | ('r0'),
22 | ('r1');
23 |
24 | INSERT INTO containers_2 (id, root_id) VALUES
25 | ('c0', 'r0'),
26 | ('c1', 'r0'),
27 | ('c2', 'r1'),
28 | ('c3', 'r1');
29 |
30 | INSERT INTO lista_2 (id, container_id) VALUES
31 | ('a00', 'c0'),
32 | ('a01', 'c0'),
33 | ('a10', 'c1'),
34 | ('a11', 'c1'),
35 | ('a20', 'c2'),
36 | ('a21', 'c2'),
37 | ('a30', 'c3'),
38 | ('a31', 'c3');
39 |
40 | INSERT INTO listb_2 (id, container_id) VALUES
41 | ('b00', 'c0'),
42 | ('b01', 'c0'),
43 | ('b10', 'c1'),
44 | ('b11', 'c1'),
45 | ('b20', 'c2'),
46 | ('b21', 'c2'),
47 | ('b30', 'c3'),
48 | ('b31', 'c3');
49 |
--------------------------------------------------------------------------------
/testdata/oracle/filter-order-offset-limit.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE root (
2 | id VARCHAR(100) PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE lista (
6 | id VARCHAR(100) PRIMARY KEY,
7 | root_id VARCHAR(100),
8 | a_elem VARCHAR(100)
9 | );
10 |
11 | CREATE TABLE listb (
12 | id VARCHAR(100) PRIMARY KEY,
13 | root_id VARCHAR(100),
14 | b_elem INTEGER
15 | );
16 |
17 | INSERT INTO root (id) VALUES
18 | ('r0'),
19 | ('r1');
20 |
21 | INSERT INTO lista (id, root_id, a_elem) VALUES
22 | ('a0', 'r0', 'foo'),
23 | ('a1', 'r0', 'bar'),
24 | ('a2', 'r0', 'baz'),
25 | ('a3', 'r0', 'quux'),
26 | ('a4', 'r1', 'foo1'),
27 | ('a5', 'r1', 'bar1'),
28 | ('a6', 'r1', 'baz1'),
29 | ('a7', 'r1', 'quux1');
30 |
31 | INSERT INTO listb (id, root_id, b_elem) VALUES
32 | ('b0', 'r0', 23),
33 | ('b1', 'r0', 13),
34 | ('b2', 'r0', 17),
35 | ('b3', 'r0', 11),
36 | ('b4', 'r1', 231),
37 | ('b5', 'r1', 131),
38 | ('b6', 'r1', 171),
39 | ('b7', 'r1', 111);
40 |
--------------------------------------------------------------------------------
/testdata/oracle/graph.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE graph_node (
2 | id INTEGER PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE graph_edge (
6 | id INTEGER PRIMARY KEY,
7 | a INTEGER,
8 | b INTEGER
9 | );
10 |
11 | INSERT INTO graph_node (id) VALUES
12 | ('1'),
13 | ('2'),
14 | ('3'),
15 | ('4');
16 |
17 | INSERT INTO graph_edge (id, a, b) VALUES
18 | ('12', '1', '2'),
19 | ('13', '1', '3'),
20 | ('24', '2', '4'),
21 | ('34', '3', '4');
22 |
--------------------------------------------------------------------------------
/testdata/oracle/interfaces.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE entities (
2 | id VARCHAR(100) PRIMARY KEY,
3 | entity_type INTEGER NOT NULL,
4 | title VARCHAR(100),
5 | synopsis_short VARCHAR(100),
6 | synopsis_long VARCHAR(100),
7 | film_rating VARCHAR(100),
8 | film_label INTEGER,
9 | series_number_of_episodes INTEGER,
10 | series_label VARCHAR(100),
11 | image_url VARCHAR(100),
12 | hidden_image_url VARCHAR(100)
13 | );
14 |
15 | CREATE TABLE episodes (
16 | id VARCHAR(100) PRIMARY KEY,
17 | series_id VARCHAR(100) NOT NULL,
18 | title VARCHAR(100),
19 | synopsis_short VARCHAR(100),
20 | synopsis_long VARCHAR(100)
21 | );
22 |
23 | INSERT INTO entities (id, entity_type, title, synopsis_short, synopsis_long, film_rating, film_label, series_number_of_episodes, series_label, image_url, hidden_image_url) VALUES
24 | ('1', 1, 'Film 1', 'Short film 1', 'Long film 1', 'PG', 1, NULL, NULL, 'http://www.example.com/film1.jpg', NULL),
25 | ('2', 1, 'Film 2', 'Short film 2', 'Long film 2', 'U', 2, NULL, NULL, 'http://www.example.com/film2.jpg', NULL),
26 | ('3', 1, 'Film 3', 'Short film 3', 'Long film 3', '15', 3, NULL, NULL, 'http://www.example.com/film3.jpg', NULL),
27 | ('4', 2, 'Series 1', 'Short series 1', 'Long series 1', NULL, NULL, 5, 'One', NULL, 'hidden_series1.jpg'),
28 | ('5', 2, 'Series 2', 'Short series 2', 'Long series 2', NULL, NULL, 6, 'Two', NULL, 'hidden_series2.jpg'),
29 | ('6', 2, 'Series 3', 'Short series 3', 'Long series 3', NULL, NULL, 7, 'Three', NULL, 'hidden_series3.jpg');
30 |
31 | INSERT INTO episodes (id, series_id, title, synopsis_short, synopsis_long) VALUES
32 | ('1', '4', 'S1E1', 'Short S1E1', 'Long S1E1'),
33 | ('2', '4', 'S1E2', 'Short S1E2', 'Long S1E2'),
34 | ('3', '5', 'S2E1', 'Short S2E1', 'Long S2E1'),
35 | ('4', '5', 'S2E2', 'Short S2E2', 'Long S2E2'),
36 | ('5', '6', 'S3E1', 'Short S3E1', 'Long S3E1'),
37 | ('6', '6', 'S3E2', 'Short S3E2', 'Long S3E2');
38 |
--------------------------------------------------------------------------------
/testdata/oracle/jsonb.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE records (
2 | id Integer PRIMARY KEY,
3 | record JSON
4 | );
5 |
6 | INSERT INTO records (id, record) VALUES
7 | ('1', '{"bool":true,"int":1,"float":1.3,"string":"foo","id":"Foo","array":[1,2,3],"choice":"ONE","object":{"id":"obj0","aField":27},"children":[{"id":"a0","aField":11},{"id":"b0","bField":"wibble"}]}'),
8 | ('2', '{"bool":false,"int":2,"float":2.4,"string":"bar","id":"Bar","array":[4,5,6],"choice":"TWO","object":{"id":"obj1","aField":28},"children":[{"id":"a1","aField":12},{"id":"b1","bField":"wobble"}]}'),
9 | ('3', '{"bool":true,"int":3,"float":3.5,"string":"baz","id":"Baz","array":[7,8,9],"choice":"THREE","object":{"id":"obj2","aField":29},"children":[{"id":"a2","aField":13},{"id":"b2","bField":"quux"}]}');
10 |
--------------------------------------------------------------------------------
/testdata/oracle/like.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE likes (
2 | id INTEGER PRIMARY KEY,
3 | notnullable VARCHAR(100) NOT NULL,
4 | nullable VARCHAR(100)
5 | );
6 |
7 | INSERT INTO likes (id, notnullable, nullable) VALUES
8 | (1, 'foo', NULL),
9 | (2, 'bar', 'baz');
10 |
--------------------------------------------------------------------------------
/testdata/oracle/mutation.sql:
--------------------------------------------------------------------------------
1 |
2 | -- A sequence to test out inserting into a table.
3 | CREATE SEQUENCE city_id START WITH 5000;
4 |
--------------------------------------------------------------------------------
/testdata/oracle/projection.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE level0 (
2 | id VARCHAR2(100) PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE level1 (
6 | id VARCHAR2(100) PRIMARY KEY,
7 | level0_id VARCHAR2(100)
8 | );
9 |
10 | CREATE TABLE level2 (
11 | id VARCHAR2(100) PRIMARY KEY,
12 | level1_id VARCHAR2(100),
13 | attr BOOLEAN
14 | );
15 |
16 | INSERT INTO level0 (id) VALUES
17 | ('00'),
18 | ('01');
19 |
20 | INSERT INTO level1 (id, level0_id) VALUES
21 | ('10', '00'),
22 | ('11', '00'),
23 | ('12', '01'),
24 | ('13', '01');
25 |
26 | INSERT INTO level2 (id, level1_id, attr) VALUES
27 | ('20', '10', 'FALSE'),
28 | ('21', '10', 'FALSE'),
29 | ('22', '11', 'FALSE'),
30 | ('23', '11', 'FALSE'),
31 | ('24', '12', 'TRUE'),
32 | ('25', '12', 'FALSE'),
33 | ('26', '13', 'FALSE'),
34 | ('27', '13', 'FALSE');
35 |
--------------------------------------------------------------------------------
/testdata/oracle/recursive-interfaces.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE recursive_interface_items (
2 | id VARCHAR2(100) PRIMARY KEY,
3 | item_type INTEGER NOT NULL
4 | );
5 |
6 | CREATE TABLE recursive_interface_next_items (
7 | id VARCHAR2(100) PRIMARY KEY,
8 | next_item VARCHAR2(100)
9 | );
10 |
11 | INSERT INTO recursive_interface_items (id, item_type) VALUES
12 | ('1', '1'),
13 | ('2', '2');
14 |
15 | INSERT INTO recursive_interface_next_items (id, next_item) VALUES
16 | ('1', '2'),
17 | ('2', '1');
18 |
--------------------------------------------------------------------------------
/testdata/oracle/sibling-lists.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE seq_scan_a
2 | (
3 | id VARCHAR2(100) PRIMARY KEY
4 | );
5 |
6 | CREATE TABLE seq_scan_b
7 | (
8 | id VARCHAR2(100) PRIMARY KEY,
9 | a_id VARCHAR2(100) NOT NULL
10 | );
11 |
12 | CREATE INDEX seq_scan_b_a_id_idx ON seq_scan_b(a_id);
13 |
14 | CREATE TABLE seq_scan_c
15 | (
16 | id VARCHAR2(100) PRIMARY KEY,
17 | b_id VARCHAR2(100) NOT NULL,
18 | name_c VARCHAR2(100) NOT NULL
19 | );
20 |
21 | CREATE INDEX seq_scan_c_b_id_idx ON seq_scan_c(b_id);
22 |
23 | CREATE TABLE seq_scan_d
24 | (
25 | id VARCHAR2(100) PRIMARY KEY,
26 | b_id VARCHAR2(100) NOT NULL,
27 | name_d VARCHAR2(100) NOT NULL
28 | );
29 |
30 | CREATE INDEX seq_scan_d_b_id_idx ON seq_scan_d(b_id);
31 |
32 | INSERT INTO seq_scan_a(id) VALUES('id_a_1');
33 |
34 | INSERT INTO seq_scan_b(id, a_id) VALUES('id_b_1', 'id_a_1');
35 |
36 | INSERT INTO seq_scan_c(id, b_id, name_c) VALUES('id_c_1', 'id_b_1', 'name_c');
37 | INSERT INTO seq_scan_d(id, b_id, name_d) VALUES('id_d_1', 'id_b_1', 'name_d');
38 |
--------------------------------------------------------------------------------
/testdata/oracle/tree.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE bintree (
2 | id INTEGER PRIMARY KEY,
3 | left_child INTEGER,
4 | right_child INTEGER
5 | );
6 |
7 | INSERT INTO bintree (id, left_child, right_child) VALUES
8 | (0, 1, 2),
9 | (1, 3, 4),
10 | (2, 5, 6),
11 | (3, NULL, NULL),
12 | (4, NULL, NULL),
13 | (5, NULL, NULL),
14 | (6, NULL, NULL);
15 |
--------------------------------------------------------------------------------
/testdata/oracle/unions.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE collections (
2 | id VARCHAR2(100) PRIMARY KEY,
3 | item_type VARCHAR2(100) NOT NULL,
4 | itema VARCHAR2(100),
5 | itemb VARCHAR2(100)
6 | );
7 |
8 | INSERT INTO collections (id, item_type, itema, itemb) VALUES
9 | ('1', 'ItemA', 'A', NULL),
10 | ('2', 'ItemB', NULL, 'B');
11 |
--------------------------------------------------------------------------------
/testdata/pg/array-join.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE array_join_root (
2 | id VARCHAR PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE array_join_list_a (
6 | id VARCHAR PRIMARY KEY,
7 | root_id VARCHAR,
8 | a_elem VARCHAR[]
9 | );
10 |
11 | CREATE TABLE array_join_list_b (
12 | id VARCHAR PRIMARY KEY,
13 | root_id VARCHAR,
14 | b_elem INTEGER
15 | );
16 |
17 | COPY array_join_root (id) FROM STDIN WITH DELIMITER '|';
18 | r0
19 | r1
20 | \.
21 |
22 | COPY array_join_list_a (id, root_id, a_elem) FROM STDIN WITH DELIMITER '|';
23 | a0|r0|{foo1,foo2}
24 | a1|r0|{bar1,bar2}
25 | a2|r0|{baz1,baz2}
26 | a3|r0|{quux1,quux2}
27 | a4|r1|{foo11,foo22}
28 | a5|r1|{bar11,bar22}
29 | a6|r1|{baz11,baz22}
30 | a7|r1|{quux11,quux22}
31 | \.
32 |
33 | COPY array_join_list_b (id, root_id, b_elem) FROM STDIN WITH DELIMITER '|';
34 | b0|r0|23
35 | b1|r0|13
36 | b2|r0|17
37 | b3|r0|11
38 | b4|r1|231
39 | b5|r1|131
40 | b6|r1|171
41 | b7|r1|111
42 | \.
43 |
--------------------------------------------------------------------------------
/testdata/pg/coalesce.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE r (
2 | id TEXT PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE ca (
6 | id TEXT PRIMARY KEY,
7 | rid TEXT NOT NULL,
8 | a INTEGER NOT NULL
9 | );
10 |
11 | CREATE TABLE cb (
12 | id TEXT PRIMARY KEY,
13 | rid TEXT NOT NULL,
14 | b BOOLEAN NOT NULL
15 | );
16 |
17 | CREATE TABLE cc (
18 | id TEXT PRIMARY KEY,
19 | rid TEXT NOT NULL,
20 | c TIMESTAMP WITH TIME ZONE NOT NULL
21 | );
22 |
23 | COPY r (id) FROM STDIN WITH DELIMITER '|';
24 | R1
25 | R2
26 | R3
27 | \.
28 |
29 | COPY ca (id, rid, a) FROM STDIN WITH DELIMITER '|';
30 | CA1a|R1|10
31 | CA1b|R1|11
32 | CA2|R2|20
33 | CA3|R3|30
34 | \.
35 |
36 | COPY cb (id, rid, b) FROM STDIN WITH DELIMITER '|';
37 | CB2a|R2|TRUE
38 | CB2b|R2|FALSE
39 | CB3|R3|TRUE
40 | \.
41 |
42 | COPY cc (id, rid, c) FROM STDIN WITH DELIMITER '|';
43 | CC1|R1|2020-05-27T21:00:00+02
44 | CC2|R2|2004-10-19T10:23:54+02
45 | CC3|R3|2014-10-19T10:23:54+02
46 | \.
47 |
--------------------------------------------------------------------------------
/testdata/pg/composite-keys.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE composite_key_parent (
2 | key_1 INTEGER NOT NULL,
3 | key_2 VARCHAR NOT NULL,
4 | PRIMARY KEY (key_1, key_2)
5 | );
6 |
7 | CREATE TABLE composite_key_child (
8 | id INTEGER PRIMARY KEY,
9 | parent_1 INTEGER NOT NULL,
10 | parent_2 VARCHAR NOT NULL,
11 | FOREIGN KEY (parent_1, parent_2) REFERENCES composite_key_parent (key_1, key_2)
12 | );
13 |
14 | COPY composite_key_parent (key_1, key_2) FROM STDIN WITH DELIMITER '|';
15 | 1|foo
16 | 1|bar
17 | 2|foo
18 | 2|bar
19 | \.
20 |
21 | COPY composite_key_child (id, parent_1, parent_2) FROM STDIN WITH DELIMITER '|';
22 | 1|1|foo
23 | 2|1|bar
24 | 3|2|foo
25 | 4|2|bar
26 | \.
27 |
--------------------------------------------------------------------------------
/testdata/pg/cursor-json.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE brands (
2 | id Int PRIMARY KEY,
3 | categories Int
4 | );
5 |
6 | COPY brands (id, categories) FROM STDIN WITH DELIMITER '|';
7 | 1|8
8 | 2|16
9 | \.
10 |
--------------------------------------------------------------------------------
/testdata/pg/embedding.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE films (
2 | title TEXT PRIMARY KEY,
3 | synopsis_short TEXT,
4 | synopsis_long TEXT
5 | );
6 |
7 | CREATE TABLE series (
8 | title TEXT PRIMARY KEY,
9 | synopsis_short TEXT,
10 | synopsis_long TEXT
11 | );
12 |
13 | CREATE TABLE episodes2 (
14 | title TEXT PRIMARY KEY,
15 | series_title TEXT NOT NULL,
16 | synopsis_short TEXT,
17 | synopsis_long TEXT
18 | );
19 |
20 | COPY films (title, synopsis_short, synopsis_long) FROM STDIN WITH DELIMITER '|';
21 | Film 1|Short film 1|Long film 1
22 | Film 2|Short film 2|Long film 2
23 | Film 3|Short film 3|Long film 3
24 | \.
25 |
26 |
27 | COPY series (title, synopsis_short, synopsis_long) FROM STDIN WITH DELIMITER '|';
28 | Series 1|Short series 1|Long series 1
29 | Series 2|Short series 2|Long series 2
30 | Series 3|Short series 3|Long series 3
31 | \.
32 |
33 | COPY episodes2 (title, series_title, synopsis_short, synopsis_long) FROM STDIN WITH DELIMITER '|';
34 | S01E01|Series 1|Short S01E01|Long S01E01
35 | S01E02|Series 1|Short S01E02|Long S01E02
36 | S01E03|Series 1|Short S01E03|Long S01E03
37 | S02E01|Series 2|Short S02E01|Long S02E01
38 | S02E02|Series 2|Short S02E02|Long S02E02
39 | S02E03|Series 2|Short S02E03|Long S02E03
40 | S03E01|Series 3|Short S03E01|Long S03E01
41 | S03E02|Series 3|Short S03E02|Long S03E02
42 | S03E03|Series 3|Short S03E03|Long S03E03
43 | \.
44 |
--------------------------------------------------------------------------------
/testdata/pg/embedding2.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE t_program (
2 | c_program_id VARCHAR NOT NULL PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE t_observation (
6 | c_program_id VARCHAR NOT NULL REFERENCES t_program(c_program_id),
7 | c_observation_id VARCHAR NOT NULL PRIMARY KEY
8 | );
9 |
10 | COPY t_program (c_program_id) FROM STDIN WITH DELIMITER '|';
11 | foo
12 | bar
13 | \.
14 |
15 | COPY t_observation (c_program_id, c_observation_id) FROM STDIN WITH DELIMITER '|';
16 | foo|fo1
17 | foo|fo2
18 | bar|bo1
19 | bar|bo2
20 | \.
21 |
--------------------------------------------------------------------------------
/testdata/pg/filter-join-alias.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE episodes3 (
2 | id VARCHAR,
3 | name VARCHAR,
4 | PRIMARY KEY (id, name)
5 | );
6 |
7 | CREATE TABLE images3 (
8 | public_url VARCHAR PRIMARY KEY,
9 | id VARCHAR NOT NULL,
10 | name VARCHAR NOT NULL
11 | );
12 |
13 | INSERT INTO episodes3 (id, name) VALUES ('a', 'abc');
14 |
15 | INSERT INTO images3 (public_url, id, name) VALUES ('test', 'a', 'abc');
16 |
--------------------------------------------------------------------------------
/testdata/pg/filter-order-offset-limit-2.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE root_2 (
2 | id VARCHAR PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE containers_2 (
6 | id VARCHAR PRIMARY KEY,
7 | root_id VARCHAR
8 | );
9 |
10 | CREATE TABLE lista_2 (
11 | id VARCHAR PRIMARY KEY,
12 | container_id VARCHAR
13 | );
14 |
15 | CREATE TABLE listb_2 (
16 | id VARCHAR PRIMARY KEY,
17 | container_id VARCHAR
18 | );
19 |
20 | COPY root_2 (id) FROM STDIN WITH DELIMITER '|';
21 | r0
22 | r1
23 | \.
24 |
25 | COPY containers_2 (id, root_id) FROM STDIN WITH DELIMITER '|';
26 | c0|r0
27 | c1|r0
28 | c2|r1
29 | c3|r1
30 | \.
31 |
32 | COPY lista_2 (id, container_id) FROM STDIN WITH DELIMITER '|';
33 | a00|c0
34 | a01|c0
35 | a10|c1
36 | a11|c1
37 | a20|c2
38 | a21|c2
39 | a30|c3
40 | a31|c3
41 | \.
42 |
43 | COPY listb_2 (id, container_id) FROM STDIN WITH DELIMITER '|';
44 | b00|c0
45 | b01|c0
46 | b10|c1
47 | b11|c1
48 | b20|c2
49 | b21|c2
50 | b30|c3
51 | b31|c3
52 | \.
53 |
--------------------------------------------------------------------------------
/testdata/pg/filter-order-offset-limit.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE root (
2 | id VARCHAR PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE lista (
6 | id VARCHAR PRIMARY KEY,
7 | root_id VARCHAR,
8 | a_elem VARCHAR
9 | );
10 |
11 | CREATE TABLE listb (
12 | id VARCHAR PRIMARY KEY,
13 | root_id VARCHAR,
14 | b_elem INTEGER
15 | );
16 |
17 | COPY root (id) FROM STDIN WITH DELIMITER '|';
18 | r0
19 | r1
20 | \.
21 |
22 | COPY lista (id, root_id, a_elem) FROM STDIN WITH DELIMITER '|';
23 | a0|r0|foo
24 | a1|r0|bar
25 | a2|r0|baz
26 | a3|r0|quux
27 | a4|r1|foo1
28 | a5|r1|bar1
29 | a6|r1|baz1
30 | a7|r1|quux1
31 | \.
32 |
33 | COPY listb (id, root_id, b_elem) FROM STDIN WITH DELIMITER '|';
34 | b0|r0|23
35 | b1|r0|13
36 | b2|r0|17
37 | b3|r0|11
38 | b4|r1|231
39 | b5|r1|131
40 | b6|r1|171
41 | b7|r1|111
42 | \.
43 |
--------------------------------------------------------------------------------
/testdata/pg/graph.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE graph_node (
2 | id INTEGER PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE graph_edge (
6 | id INTEGER PRIMARY KEY,
7 | a INTEGER,
8 | b INTEGER
9 | );
10 |
11 | COPY graph_node (id) FROM STDIN WITH DELIMITER '|';
12 | 1
13 | 2
14 | 3
15 | 4
16 | \.
17 |
18 | COPY graph_edge (id, a, b) FROM STDIN WITH DELIMITER '|';
19 | 12|1|2
20 | 13|1|3
21 | 24|2|4
22 | 34|3|4
23 | \.
24 |
--------------------------------------------------------------------------------
/testdata/pg/interfaces.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE entities (
2 | id TEXT PRIMARY KEY,
3 | entity_type INTEGER NOT NULL,
4 | title TEXT,
5 | synopsis_short TEXT,
6 | synopsis_long TEXT,
7 | film_rating TEXT,
8 | film_label INTEGER,
9 | series_number_of_episodes INTEGER,
10 | series_label TEXT,
11 | image_url TEXT,
12 | hidden_image_url TEXT
13 | );
14 |
15 | CREATE TABLE episodes (
16 | id TEXT PRIMARY KEY,
17 | series_id TEXT NOT NULL,
18 | title TEXT,
19 | synopsis_short TEXT,
20 | synopsis_long TEXT
21 | );
22 |
23 | COPY entities (id, entity_type, title, synopsis_short, synopsis_long, film_rating, film_label, series_number_of_episodes, series_label, image_url, hidden_image_url) FROM STDIN WITH DELIMITER '|' NULL AS '';
24 | 1|1|Film 1|Short film 1|Long film 1|PG|1|||http://www.example.com/film1.jpg|
25 | 2|1|Film 2|Short film 2|Long film 2|U|2|||http://www.example.com/film2.jpg|
26 | 3|1|Film 3|Short film 3|Long film 3|15|3|||http://www.example.com/film3.jpg|
27 | 4|2|Series 1|Short series 1|Long series 1|||5|One||hidden_series1.jpg
28 | 5|2|Series 2|Short series 2|Long series 2|||6|Two||hidden_series2.jpg
29 | 6|2|Series 3|Short series 3|Long series 3|||7|Three||hidden_series3.jpg
30 | \.
31 |
32 | COPY episodes (id, series_id, title, synopsis_short, synopsis_long) FROM STDIN WITH DELIMITER '|' NULL AS '';
33 | 1|4|S1E1|Short S1E1|Long S1E1
34 | 2|4|S1E2|Short S1E2|Long S1E2
35 | 3|5|S2E1|Short S2E1|Long S2E1
36 | 4|5|S2E2|Short S2E2|Long S2E2
37 | 5|6|S3E1|Short S3E1|Long S3E1
38 | 6|6|S3E2|Short S3E2|Long S3E2
39 | \.
40 |
--------------------------------------------------------------------------------
/testdata/pg/jsonb.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE records (
2 | id Int PRIMARY KEY,
3 | record JSONB
4 | );
5 |
6 | COPY records (id, record) FROM STDIN WITH DELIMITER '|';
7 | 1|{"bool":true,"int":1,"float":1.3,"string":"foo","id":"Foo","array":[1,2,3],"choice":"ONE","object":{"id":"obj0","aField":27},"children":[{"id":"a0","aField":11},{"id":"b0","bField":"wibble"}]}
8 | 2|{"bool":false,"int":2,"float":2.4,"string":"bar","id":"Bar","array":[4,5,6],"choice":"TWO","object":{"id":"obj1","aField":28},"children":[{"id":"a1","aField":12},{"id":"b1","bField":"wobble"}]}
9 | 3|{"bool":true,"int":3,"float":3.5,"string":"baz","id":"Baz","array":[7,8,9],"choice":"THREE","object":{"id":"obj2","aField":29},"children":[{"id":"a2","aField":13},{"id":"b2","bField":"quux"}]}
10 | \.
11 |
--------------------------------------------------------------------------------
/testdata/pg/like.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE likes (
2 | id INTEGER PRIMARY KEY,
3 | notnullable TEXT NOT NULL,
4 | nullable TEXT
5 | );
6 |
7 | COPY likes (id, notnullable, nullable) FROM STDIN WITH DELIMITER '|';
8 | 1|foo|\N
9 | 2|bar|baz
10 | \.
11 |
--------------------------------------------------------------------------------
/testdata/pg/movies.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE movies (
2 | id UUID PRIMARY KEY,
3 | title TEXT NOT NULL,
4 | genre INTEGER NOT NULL,
5 | releasedate DATE NOT NULL,
6 | showtime TIME NOT NULL,
7 | nextshowing TIMESTAMP WITH TIME ZONE NOT NULL,
8 | duration BIGINT NOT NULL,
9 | categories VARCHAR[] NOT NULL,
10 | features VARCHAR[] NOT NULL,
11 | tags INTEGER NOT NULL
12 | );
13 |
14 | COPY movies (id, title, genre, releasedate, showtime, nextshowing, duration, categories, features, tags) FROM STDIN WITH DELIMITER '|';
15 | 6a7837fc-b463-4d32-b628-0f4b3065cb21|Celine et Julie Vont en Bateau|1|1974-10-07|19:35:00|2020-05-22T19:35:00Z|12300000|{"drama","comedy"}|{"hd","hls"}|1
16 | 11daf8c0-11c3-4453-bfe1-cb6e6e2f9115|Duelle|1|1975-09-15|19:20:00|2020-05-27T19:20:00Z|7260000|{"drama"}|{"hd"}|1
17 | aea9756f-621b-42d5-b130-71f3916c4ba3|L'Amour fou|1|1969-01-15|21:00:00|2020-05-27T21:00:00Z|15120000|{"drama"}|{"hd"}|2
18 | 2ddb041f-86c2-4bd3-848c-990a3862634e|Last Year at Marienbad|1|1961-06-25|20:30:00|2020-05-26T20:30:00Z|5640000|{"drama"}|{"hd"}|5
19 | 8ae5b13b-044c-4ff0-8b71-ccdb7d77cd88|Zazie dans le Métro|3|1960-10-28|20:15:00|2020-05-25T20:15:00Z|5340000|{"drama","comedy"}|{"hd"}|3
20 | 9dce9deb-9188-4cc2-9685-9842b8abdd34|Alphaville|2|1965-05-05|19:45:00|2020-05-19T19:45:00Z|5940000|{"drama","science fiction"}|{"hd"}|4
21 | 1bf00ac6-91ab-4e51-b686-3fd5e2324077|Stalker|1|1979-05-13|15:30:00|2020-05-19T15:30:00Z|9660000|{"drama","science fiction"}|{"hd"}|7
22 | 6a878e06-6563-4a0c-acd9-d28dcfb2e91a|Weekend|3|1967-12-29|22:30:00|2020-05-19T22:30:00Z|6300000|{"drama","comedy"}|{"hd"}|2
23 | 2a40415c-ea6a-413f-bbef-a80ae280c4ff|Daisies|3|1966-12-30|21:30:00|2020-05-15T21:30:00Z|4560000|{"drama","comedy"}|{"hd"}|6
24 | 2f6dcb0a-4122-4a21-a1c6-534744dd6b85|Le Pont du Nord|1|1982-01-13|20:45:00|2020-05-11T20:45:00Z|7620000|{"drama"}|{"hd"}|3
25 | \.
26 |
--------------------------------------------------------------------------------
/testdata/pg/mutation.sql:
--------------------------------------------------------------------------------
1 |
2 | -- A sequence to test out inserting into a table.
3 | CREATE SEQUENCE city_id START WITH 5000;
4 |
--------------------------------------------------------------------------------
/testdata/pg/projection.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE level0 (
2 | id VARCHAR PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE level1 (
6 | id VARCHAR PRIMARY KEY,
7 | level0_id VARCHAR
8 | );
9 |
10 | CREATE TABLE level2 (
11 | id VARCHAR PRIMARY KEY,
12 | level1_id VARCHAR,
13 | attr BOOLEAN
14 | );
15 |
16 | COPY level0 (id) FROM STDIN WITH DELIMITER '|';
17 | 00
18 | 01
19 | \.
20 |
21 | COPY level1 (id, level0_id) FROM STDIN WITH DELIMITER '|';
22 | 10|00
23 | 11|00
24 | 12|01
25 | 13|01
26 | \.
27 |
28 | COPY level2 (id, level1_id, attr) FROM STDIN WITH DELIMITER '|';
29 | 20|10|false
30 | 21|10|false
31 | 22|11|false
32 | 23|11|false
33 | 24|12|true
34 | 25|12|false
35 | 26|13|false
36 | 27|13|false
37 | \.
38 |
--------------------------------------------------------------------------------
/testdata/pg/recursive-interfaces.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE recursive_interface_items (
2 | id TEXT PRIMARY KEY,
3 | item_type INTEGER NOT NULL
4 | );
5 |
6 | CREATE TABLE recursive_interface_next_items (
7 | id TEXT PRIMARY KEY,
8 | next_item TEXT
9 | );
10 |
11 | COPY recursive_interface_items (id, item_type) FROM STDIN WITH DELIMITER '|' NULL AS '';
12 | 1|1
13 | 2|2
14 | \.
15 |
16 | COPY recursive_interface_next_items (id, next_item) FROM STDIN WITH DELIMITER '|' NULL AS '';
17 | 1|2
18 | 2|1
19 | \.
20 |
--------------------------------------------------------------------------------
/testdata/pg/sibling-lists.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE seq_scan_a
2 | (
3 | id VARCHAR PRIMARY KEY
4 | );
5 |
6 | CREATE TABLE seq_scan_b
7 | (
8 | id VARCHAR PRIMARY KEY,
9 | a_id VARCHAR NOT NULL
10 | );
11 |
12 | CREATE INDEX seq_scan_b_a_id_idx ON seq_scan_b(a_id);
13 |
14 | CREATE TABLE seq_scan_c
15 | (
16 | id VARCHAR PRIMARY KEY,
17 | b_id VARCHAR NOT NULL,
18 | name_c VARCHAR NOT NULL
19 | );
20 |
21 | CREATE INDEX seq_scan_c_b_id_idx ON seq_scan_c(b_id);
22 |
23 | CREATE TABLE seq_scan_d
24 | (
25 | id VARCHAR PRIMARY KEY,
26 | b_id VARCHAR NOT NULL,
27 | name_d VARCHAR NOT NULL
28 | );
29 |
30 | CREATE INDEX seq_scan_d_b_id_idx ON seq_scan_d(b_id);
31 |
32 | INSERT INTO seq_scan_a(id) VALUES('id_a_1');
33 |
34 | INSERT INTO seq_scan_b(id, a_id) VALUES('id_b_1', 'id_a_1');
35 |
36 | INSERT INTO seq_scan_c(id, b_id, name_c) VALUES('id_c_1', 'id_b_1', 'name_c');
37 | INSERT INTO seq_scan_d(id, b_id, name_d) VALUES('id_d_1', 'id_b_1', 'name_d');
38 |
--------------------------------------------------------------------------------
/testdata/pg/tree.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE bintree (
2 | id INTEGER PRIMARY KEY,
3 | left_child INTEGER,
4 | right_child INTEGER
5 | );
6 |
7 | COPY bintree (id, left_child, right_child) FROM STDIN WITH DELIMITER '|';
8 | 0|1|2
9 | 1|3|4
10 | 2|5|6
11 | 3|\N|\N
12 | 4|\N|\N
13 | 5|\N|\N
14 | 6|\N|\N
15 | \.
16 |
--------------------------------------------------------------------------------
/testdata/pg/unions.sql:
--------------------------------------------------------------------------------
1 |
2 | CREATE TABLE collections (
3 | id TEXT PRIMARY KEY,
4 | item_type TEXT NOT NULL,
5 | itema TEXT,
6 | itemb TEXT
7 | );
8 |
9 | COPY collections (id, item_type, itema, itemb) FROM STDIN WITH DELIMITER '|' NULL AS '';
10 | 1|ItemA|A|null
11 | 2|ItemB|null|B
12 | \.
13 |
--------------------------------------------------------------------------------