├── .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/.*.*//') 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 Cats friendly 2 | 3 | [![Build Status](https://github.com/typelevel/grackle/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/typelevel/grackle/actions?query=branch%3Amain+workflow%3A%22Continuous+Integration%22) 4 | [![Maven Central](https://img.shields.io/maven-central/v/org.typelevel/grackle-core_2.13?versionPrefix=0)](https://img.shields.io/maven-central/v/org.typelevel/grackle-core_2.13?versionPrefix=0) 5 | [![javadoc](https://javadoc.io/badge2/org.typelevel/grackle-core_2.13/javadoc.svg)](https://javadoc.io/doc/org.typelevel/grackle-core_2.13) 6 | [![Typelevel library](https://img.shields.io/badge/typelevel-library-green.svg)](https://typelevel.org/projects/#grackle) 7 | [![codecov](https://codecov.io/gh/typelevel/grackle/branch/main/graph/badge.svg)](https://codecov.io/gh/typelevel/grackle) 8 | [![Discord](https://img.shields.io/discord/632277896739946517.svg?label=&logo=discord&logoColor=ffffff&color=404244&labelColor=6A7EC2)][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 | --------------------------------------------------------------------------------