diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf2aff8..037deb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,10 +85,56 @@ jobs: name: wasm-file path: dist/plugin.wasm - end2end-tests: + codegen_test: + name: Codegen Test + needs: [build] + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + ruby-version: [ '3.3' ] + generate-types: [ 'true' ] + generate-gemfile: [ 'false' ] + + steps: + - name: Check out Git repository + uses: actions/checkout@v4 + + - name: Load .env file + uses: xom9ikk/dotenv@v2.3.0 + with: + load-mode: strict + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + + - uses: actions/download-artifact@v4 + with: + name: wasm-file + path: dist + + - uses: sqlc-dev/setup-sqlc@v4 + with: + sqlc-version: '1.25.0' + + - name: Updating plugin sha + run: ./scripts/wasm/update_sha.sh ${SQLC_CI_FILE} + + - name: Codegen Test + run: ./scripts/tests/run_codegen.sh ${SQLC_CI_FILE} \ + ${{ matrix.ruby-version }} ${{ matrix.generate-types }} ${{ matrix.generate-gemfile }} + + - name: Steep check + if: ${{ matrix.generate-types == 'true' }} + run: rake steep + + end2end_tests: name: End-to-End Tests runs-on: ubuntu-latest - needs: [build] + needs: [build, codegen_test] steps: - uses: actions/checkout@v4 diff --git a/Drivers/MethodGen.cs b/Drivers/MethodGen.cs index 9d43582..4bc40fe 100644 --- a/Drivers/MethodGen.cs +++ b/Drivers/MethodGen.cs @@ -30,7 +30,7 @@ public MethodDeclaration OneDeclare(string funcName, string queryTextConstant, s funcName, argInterface, GetMethodArgs(argInterface, parameters), - returnInterface, + $"{returnInterface}?", new List { new WithResource(Variable.Pool.AsProperty(), Variable.Client.AsVar(), withResourceBody.ToList()) @@ -56,8 +56,11 @@ public MethodDeclaration ManyDeclare(string funcName, string queryTextConstant, dbDriver.PrepareStmt(funcName, queryTextConstant), ExecuteAndAssign(funcName, queryParams), new SimpleStatement(Variable.Entities.AsVar(), new SimpleExpression("[]")), - new ForeachLoop(Variable.Result.AsVar(), Variable.Row.AsVar(), - new List { listAppend }), + new ForeachLoop( + Variable.Result.AsVar(), + Variable.Row.AsVar(), + new List { listAppend } + ), new SimpleExpression($"return {Variable.Entities.AsVar()}") ] ); @@ -66,10 +69,14 @@ public MethodDeclaration ManyDeclare(string funcName, string queryTextConstant, funcName, argInterface, GetMethodArgs(argInterface, parameters), - returnInterface, + $"Array[{returnInterface}]", new List { - new WithResource(Variable.Pool.AsProperty(), Variable.Client.AsVar(), withResourceBody.ToList()) + new WithResource( + Variable.Pool.AsProperty(), + Variable.Client.AsVar(), + withResourceBody.ToList() + ) }); } diff --git a/Gemfile b/Gemfile index 2154a85..9382f7b 100644 --- a/Gemfile +++ b/Gemfile @@ -4,4 +4,5 @@ gem 'connection_pool' gem 'mysql2' gem 'pg', '1.5.2' gem 'minitest' -gem 'rake' \ No newline at end of file +gem 'rake' +gem 'steep' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 510b5a3..4ff2511 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,11 +1,73 @@ GEM remote: https://rubygems.org/ specs: + abbrev (0.1.2) + activesupport (7.1.3.3) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + ast (2.4.2) + base64 (0.2.0) + bigdecimal (3.1.5) + concurrent-ruby (1.2.3) connection_pool (2.4.1) + csv (3.2.8) + drb (2.2.0) + ruby2_keywords + ffi (1.16.3) + fileutils (1.7.2) + i18n (1.14.5) + concurrent-ruby (~> 1.0) + json (2.7.1) + language_server-protocol (3.17.0.3) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.6.0) minitest (5.24.0) + mutex_m (0.2.0) mysql2 (0.5.6) + parser (3.3.1.0) + ast (~> 2.4.1) + racc pg (1.5.2) + racc (1.7.3) + rainbow (3.1.1) rake (13.2.1) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + rbs (3.4.0) + abbrev + ruby2_keywords (0.0.5) + securerandom (0.3.1) + steep (1.6.0) + activesupport (>= 5.1) + concurrent-ruby (>= 1.1.10) + csv (>= 3.0.9) + fileutils (>= 1.1.0) + json (>= 2.1.0) + language_server-protocol (>= 3.15, < 4.0) + listen (~> 3.0) + logger (>= 1.3.0) + parser (>= 3.1) + rainbow (>= 2.2.2, < 4.0) + rbs (>= 3.1.0) + securerandom (>= 0.1) + strscan (>= 1.0.0) + terminal-table (>= 2, < 4) + strscan (3.0.7) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (2.5.0) PLATFORMS arm64-darwin-23 @@ -17,6 +79,7 @@ DEPENDENCIES mysql2 pg (= 1.5.2) rake + steep BUNDLED WITH 2.5.10 diff --git a/PluginOptions/RubyVersion.cs b/PluginOptions/RubyVersion.cs index 6778478..d11bbb8 100644 --- a/PluginOptions/RubyVersion.cs +++ b/PluginOptions/RubyVersion.cs @@ -64,6 +64,6 @@ public static bool ImmutableDataSupported(this RubyVersion me) return me.AtLeast(new RubyVersion(3, 2)); } - [GeneratedRegex(@"^(0|[1-9][0-9]*)(?:\.(0|[1-9][0-9]*))?(?:\.(0|[1-9][0-9]*))?$")] + [GeneratedRegex(@"^\s*(0|[1-9][0-9]*)(?:\.(0|[1-9][0-9]*))?(?:\.(0|[1-9][0-9]*))?\s*$")] private static partial Regex SemanticVersionRegex(); } \ No newline at end of file diff --git a/Rakefile b/Rakefile index bc92fd0..96db2b9 100755 --- a/Rakefile +++ b/Rakefile @@ -6,6 +6,10 @@ Rake::TestTask.new(:end2end_tests) do |t| # called inside Docker t.pattern = "tests/end2end_*.rb" end +task :steep do + sh "bundle exec steep check --with-expectations" +end + task :run_end2end_tests do sh "./scripts/tests/run_end2end.sh" end diff --git a/RubySyntax/Flows.cs b/RubySyntax/Flows.cs index cd92e27..55c081a 100644 --- a/RubySyntax/Flows.cs +++ b/RubySyntax/Flows.cs @@ -78,7 +78,8 @@ public string BuildCode() { var foreachBody = statements .Select(s => s.BuildCode()) - .JoinByNewLine(); + .JoinByNewLine() + .Indent(); var foreachLoop = $"{collectionVar}.each do |{controlVar}|\n{foreachBody}\nend"; return foreachLoop; } diff --git a/Steepfile b/Steepfile new file mode 100644 index 0000000..d1d22e9 --- /dev/null +++ b/Steepfile @@ -0,0 +1,4 @@ +target :gen do + check "examples" + signature "examples" +end \ No newline at end of file diff --git a/examples/mysql2/query_sql.rb b/examples/mysql2/query_sql.rb index a49f973..0cff40d 100644 --- a/examples/mysql2/query_sql.rb +++ b/examples/mysql2/query_sql.rb @@ -118,7 +118,7 @@ def list_authors result = stmt.execute entities = [] result.each do |row| - entities << ListAuthorsRow.new(row['id'], row['name'], row['bio']) + entities << ListAuthorsRow.new(row['id'], row['name'], row['bio']) end return entities end diff --git a/examples/mysql2/query_sql.rbs b/examples/mysql2/query_sql.rbs index 5ee7afe..f6e5fc1 100644 --- a/examples/mysql2/query_sql.rbs +++ b/examples/mysql2/query_sql.rbs @@ -139,9 +139,9 @@ module Mysql2Codegen @pool: untyped def initialize: (Hash[String, String], Hash[String, String]) -> void - def get_author: (GetAuthorArgs) -> GetAuthorRow + def get_author: (GetAuthorArgs) -> GetAuthorRow? - def list_authors: -> ListAuthorsRow + def list_authors: -> Array[ListAuthorsRow] def create_author: (CreateAuthorArgs) -> void @@ -151,6 +151,6 @@ module Mysql2Codegen def delete_author: (DeleteAuthorArgs) -> void - def test: -> TestRow + def test: -> TestRow? end end \ No newline at end of file diff --git a/examples/pg/query_sql.rb b/examples/pg/query_sql.rb index 9b08310..710fda1 100644 --- a/examples/pg/query_sql.rb +++ b/examples/pg/query_sql.rb @@ -97,7 +97,7 @@ def list_authors result = client.exec_prepared('list_authors') entities = [] result.each do |row| - entities << ListAuthorsRow.new(row['id'], row['name'], row['bio']) + entities << ListAuthorsRow.new(row['id'], row['name'], row['bio']) end return entities end diff --git a/examples/pg/query_sql.rbs b/examples/pg/query_sql.rbs index 64f92a5..85d7b6a 100644 --- a/examples/pg/query_sql.rbs +++ b/examples/pg/query_sql.rbs @@ -93,14 +93,14 @@ module PgCodegen @prepared_statements: Set[String] def initialize: (Hash[String, String], Hash[String, String]) -> void - def get_author: (GetAuthorArgs) -> GetAuthorRow + def get_author: (GetAuthorArgs) -> GetAuthorRow? - def list_authors: -> ListAuthorsRow + def list_authors: -> Array[ListAuthorsRow] - def create_author: (CreateAuthorArgs) -> CreateAuthorRow + def create_author: (CreateAuthorArgs) -> CreateAuthorRow? def delete_author: (DeleteAuthorArgs) -> void - def test: -> TestRow + def test: -> TestRow? end end \ No newline at end of file diff --git a/scripts/tests/run_codegen.sh b/scripts/tests/run_codegen.sh index c23d236..8acb6fd 100755 --- a/scripts/tests/run_codegen.sh +++ b/scripts/tests/run_codegen.sh @@ -3,20 +3,21 @@ set -e source .env -mapfile -t examples < <(dotnet sln list | grep Example | xargs -n 1 dirname) # TODO standardize across scripts +mapfile -t examples < <(find examples -name "*.rb" -exec dirname {} +) config_file=$1 -file_per_query=$2 -generate_csproj=$3 -target_framework=$4 +ruby_version=$2 +generate_types=$3 +generate_gemfile=$4 generated_files_cleanup() { for example_dir in "${examples[@]}" do - echo "Deleting .cs files in ${example_dir}" - find "${example_dir}/" -type f -name "*.cs" -exec rm -f {} \; - if [ "${generate_csproj}" = "true" ]; then - echo "Deleting .csproj file" && rm "${example_dir}/${example_dir}.csproj" + echo "Deleting .rb & .rbs files in ${example_dir}" + find "${example_dir}/" -type f -name "*.rb" -exec rm -f {} \; + find "${example_dir}/" -type f -name "*.rbs" -exec rm -f {} \; + if [ "${generate_gemfile}" = "true" ]; then + echo "Deleting Gemfile file" && rm "${example_dir}/Gemfile" fi done } @@ -25,60 +26,45 @@ change_config() { for ((i=0; i<${#examples[@]}; i++)); do echo "Changing configuration for project ${example_dir}" yq -i " - .sql[${i}].codegen[0].options.filePerQuery = ${file_per_query} | - .sql[${i}].codegen[0].options.generateCsproj = ${generate_csproj} | - .sql[${i}].codegen[0].options.targetFramework = \"${target_framework}\" + .sql[${i}].codegen[0].options.rubyVersion = \"${ruby_version}\" | + .sql[${i}].codegen[0].options.generateTypes = ${generate_types} | + .sql[${i}].codegen[0].options.generateGemfile = ${generate_gemfile} " "${config_file}" echo "${examples[i]} codegen config:" && yq ".sql[${i}].codegen[0]" "${config_file}" done } -check_cs_file_count() { - for example_dir in "${examples[@]}" - do - echo "Checking C# file count in ${example_dir}/" - file_count=$(find "${example_dir}/" -maxdepth 1 -name "*.cs" 2>/dev/null | wc -l) - if [[ "${file_per_query}" = "true" && "${file_count}" -le 2 ]]; then - echo "Assertion failed: Not more than 2 .cs files in the directory ${example_dir}." - return 1 - elif [[ "${file_per_query}" = "false" && "${file_count}" -ne 2 ]]; then - echo "Assertion failed: Not exactly 2 .cs files in the directory ${example_dir}." - return 1 - fi - done -} - -check_csproj_file() { - for example_dir in "${examples[@]}" - do - echo "Checking ${example_dir}.csproj file generated" - if [ ! -f "${example_dir}/${example_dir}.csproj" ]; then - echo "Assertion failed: A .csproj file is not present in the directory ${example_dir}." - return 1 - fi - done -} +#check_gemfile() { +# for example_dir in "${examples[@]}" +# do +# echo "Checking Gemfile generated" +# if [ ! -f "${example_dir}/Gemfile" ]; then +# echo "Assertion failed: A Gemfile is not present in the directory ${example_dir}." +# return 1 +# fi +# done +#} -check_project_compiles() { - if [ "${generate_csproj}" = "true" ]; then - for example_dir in "${examples[@]}" - do - echo "Checking ${example_dir} project compiles" - dotnet build "${example_dir}/" - done - fi -} +#check_project_compiles() { +# if [ "${generate_csproj}" = "true" ]; then +# for example_dir in "${examples[@]}" +# do +# echo "Checking ${example_dir} project compiles" +# dotnet build "${example_dir}/" +# done +# fi +#} generated_files_cleanup && change_config sqlc -f "${config_file}" generate -test_functions=("check_cs_file_count" "check_csproj_file" "check_project_compiles") -for test_function in "${test_functions[@]}"; do - ${test_function} - status_code=$? - if [ ${status_code} -ne 0 ]; then - echo "Function ${test_function} failed with status code ${status_code}" - exit "${status_code}" - fi - echo "Test ${test_function} passed" -done \ No newline at end of file +#test_functions=("check_csproj_file" "check_project_compiles") +#for test_function in "${test_functions[@]}"; do +# ${test_function} +# status_code=$? +# if [ ${status_code} -ne 0 ]; then +# echo "Function ${test_function} failed with status code ${status_code}" +# exit "${status_code}" +# fi +# echo "Test ${test_function} passed" +#done \ No newline at end of file diff --git a/sqlc.ci.yaml b/sqlc.ci.yaml index b3f3881..ceeb178 100644 --- a/sqlc.ci.yaml +++ b/sqlc.ci.yaml @@ -14,6 +14,7 @@ sql: options: driver: pg rubyVersion: "3.3" + generateTypes: true generateGemfile: false - schema: "examples/authors/mysql/schema.sql" queries: "examples/authors/mysql/query.sql" @@ -24,4 +25,5 @@ sql: options: driver: mysql2 rubyVersion: "3.3" + generateTypes: true generateGemfile: false diff --git a/sqlc.local.yaml b/sqlc.local.yaml index e9a7d6f..7f8d79c 100644 --- a/sqlc.local.yaml +++ b/sqlc.local.yaml @@ -13,6 +13,7 @@ sql: options: driver: pg rubyVersion: "3.3" + generateTypes: true generateGemfile: false - schema: "examples/authors/mysql/schema.sql" queries: "examples/authors/mysql/query.sql" @@ -23,4 +24,5 @@ sql: options: driver: mysql2 rubyVersion: "3.3" + generateTypes: true generateGemfile: false \ No newline at end of file diff --git a/steep_expectations.yml b/steep_expectations.yml new file mode 100644 index 0000000..9fb4fa4 --- /dev/null +++ b/steep_expectations.yml @@ -0,0 +1,55 @@ +--- +- file: examples/mysql2/query_sql.rb + diagnostics: + - range: + start: + line: 98 + character: 11 + end: + line: 98 + character: 25 + severity: WARNING + message: 'Cannot find the declaration of constant: `ConnectionPool`' + code: Ruby::UnknownConstant + - range: + start: + line: 99 + character: 4 + end: + line: 99 + character: 10 + severity: WARNING + message: 'Cannot find the declaration of constant: `Mysql2`' + code: Ruby::UnknownConstant +- file: examples/pg/query_sql.rb + diagnostics: + - range: + start: + line: 68 + character: 11 + end: + line: 68 + character: 25 + severity: WARNING + message: 'Cannot find the declaration of constant: `ConnectionPool`' + code: Ruby::UnknownConstant + - range: + start: + line: 69 + character: 13 + end: + line: 69 + character: 15 + severity: WARNING + message: 'Cannot find the declaration of constant: `PG`' + code: Ruby::UnknownConstant + - range: + start: + line: 70 + character: 34 + end: + line: 70 + character: 36 + severity: WARNING + message: 'Cannot find the declaration of constant: `PG`' + code: Ruby::UnknownConstant