├── .github ├── FUNDING.yml └── workflows │ └── build.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README_cn.md ├── README_jp.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── ktorm.base.gradle.kts │ ├── ktorm.dokka.gradle.kts │ ├── ktorm.modularity.gradle.kts │ ├── ktorm.publish.gradle.kts │ ├── ktorm.source-header-check.gradle.kts │ └── ktorm.tuples-codegen.gradle.kts ├── detekt.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ktorm-core ├── ktorm-core.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── ktorm │ │ │ ├── database │ │ │ ├── CachedRowSet.kt │ │ │ ├── CachedRowSetMetadata.kt │ │ │ ├── Database.kt │ │ │ ├── JdbcExtensions.kt │ │ │ ├── JdbcTransactionManager.kt │ │ │ ├── Keywords.kt │ │ │ ├── SpringManagedTransactionManager.kt │ │ │ ├── SqlDialect.kt │ │ │ └── TransactionManager.kt │ │ │ ├── dsl │ │ │ ├── Aggregation.kt │ │ │ ├── CaseWhen.kt │ │ │ ├── CountExpression.kt │ │ │ ├── Dml.kt │ │ │ ├── Operators.kt │ │ │ ├── Query.kt │ │ │ ├── QueryRowSet.kt │ │ │ ├── QuerySource.kt │ │ │ └── WindowFunctions.kt │ │ │ ├── entity │ │ │ ├── DefaultMethodHandler.kt │ │ │ ├── Entity.kt │ │ │ ├── EntityDml.kt │ │ │ ├── EntityExtensions.kt │ │ │ ├── EntityExtensionsApi.kt │ │ │ ├── EntityGrouping.kt │ │ │ ├── EntityImplementation.kt │ │ │ ├── EntitySequence.kt │ │ │ └── Reflections.kt │ │ │ ├── expression │ │ │ ├── SqlExpressionVisitor.kt │ │ │ ├── SqlExpressionVisitorInterceptor.kt │ │ │ ├── SqlExpressions.kt │ │ │ └── SqlFormatter.kt │ │ │ ├── logging │ │ │ ├── AndroidLoggerAdapter.kt │ │ │ ├── CommonsLoggerAdapter.kt │ │ │ ├── ConsoleLogger.kt │ │ │ ├── JdkLoggerAdapter.kt │ │ │ ├── Logger.kt │ │ │ ├── NoOpLogger.kt │ │ │ └── Slf4jLoggerAdapter.kt │ │ │ └── schema │ │ │ ├── BaseTable.kt │ │ │ ├── Column.kt │ │ │ ├── ColumnBindingHandler.kt │ │ │ ├── RefCounter.kt │ │ │ ├── SqlType.kt │ │ │ ├── SqlTypes.kt │ │ │ ├── Table.kt │ │ │ └── TypeReference.kt │ └── moditect │ │ └── module-info.java │ └── test │ ├── kotlin │ └── org │ │ └── ktorm │ │ ├── BaseTest.kt │ │ ├── database │ │ ├── CircularReferenceTest.kt │ │ ├── CompoundKeysTest.kt │ │ └── DatabaseTest.kt │ │ ├── dsl │ │ ├── AggregationTest.kt │ │ ├── DmlTest.kt │ │ ├── JoinTest.kt │ │ ├── QueryTest.kt │ │ └── WindowFunctionTest.kt │ │ └── entity │ │ ├── DataClassTest.kt │ │ ├── EntitySequenceTest.kt │ │ ├── EntityTest.kt │ │ └── InlineClassTest.kt │ └── resources │ ├── drop-data.sql │ ├── init-data.sql │ └── simplelogger.properties ├── ktorm-global ├── ktorm-global.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── ktorm │ │ │ └── global │ │ │ ├── Aggregations.kt │ │ │ ├── Dml.kt │ │ │ ├── EntitySequence.kt │ │ │ ├── Global.kt │ │ │ └── Query.kt │ └── moditect │ │ └── module-info.java │ └── test │ └── kotlin │ └── org │ └── ktorm │ └── global │ ├── BaseGlobalTest.kt │ ├── GlobalAggregationTest.kt │ ├── GlobalDatabaseTest.kt │ ├── GlobalDmlTest.kt │ ├── GlobalEntitySequenceTest.kt │ ├── GlobalEntityTest.kt │ ├── GlobalJoinTest.kt │ └── GlobalQueryTest.kt ├── ktorm-jackson ├── ktorm-jackson.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── ktorm │ │ │ └── jackson │ │ │ ├── EntityDeserializers.kt │ │ │ ├── EntitySerializers.kt │ │ │ ├── EntityTypeResolverBuilder.kt │ │ │ ├── JacksonExtensions.kt │ │ │ ├── JsonSqlType.kt │ │ │ ├── KtormModule.kt │ │ │ └── PackageVersion.kt.tmpl │ ├── moditect │ │ └── module-info.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.fasterxml.jackson.databind.Module │ └── test │ └── kotlin │ └── org │ └── ktorm │ └── jackson │ ├── JacksonAnnotationTest.kt │ └── JacksonTest.kt ├── ktorm-ksp-annotations ├── ktorm-ksp-annotations.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── ktorm │ │ │ └── ksp │ │ │ └── annotation │ │ │ ├── Column.kt │ │ │ ├── Ignore.kt │ │ │ ├── PrimaryKey.kt │ │ │ ├── References.kt │ │ │ ├── Table.kt │ │ │ └── Undefined.kt │ └── moditect │ │ └── module-info.java │ └── test │ └── kotlin │ └── org │ └── ktorm │ └── ksp │ └── annotation │ └── UndefinedTest.kt ├── ktorm-ksp-compiler-maven-plugin ├── ktorm-ksp-compiler-maven-plugin.gradle.kts └── src │ └── main │ ├── kotlin │ └── org │ │ └── ktorm │ │ └── ksp │ │ └── compiler │ │ └── maven │ │ └── KtormKspMavenPluginExtension.kt │ └── resources │ └── META-INF │ └── plexus │ └── components.xml ├── ktorm-ksp-compiler ├── ktorm-ksp-compiler.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── ktorm │ │ │ └── ksp │ │ │ └── compiler │ │ │ ├── KtormProcessorProvider.kt │ │ │ ├── formatter │ │ │ ├── CodeFormatter.kt │ │ │ ├── KtLintCodeFormatter.kt │ │ │ └── StandaloneKtLintCodeFormatter.kt │ │ │ ├── generator │ │ │ ├── AddFunctionGenerator.kt │ │ │ ├── ComponentFunctionGenerator.kt │ │ │ ├── CopyFunctionGenerator.kt │ │ │ ├── EntitySequencePropertyGenerator.kt │ │ │ ├── FileGenerator.kt │ │ │ ├── PseudoConstructorFunctionGenerator.kt │ │ │ ├── RefsClassGenerator.kt │ │ │ ├── RefsPropertyGenerator.kt │ │ │ ├── TableClassGenerator.kt │ │ │ └── UpdateFunctionGenerator.kt │ │ │ ├── parser │ │ │ └── MetadataParser.kt │ │ │ └── util │ │ │ ├── KspExtensions.kt │ │ │ ├── Namings.kt │ │ │ └── SqlTypeMappings.kt │ └── resources │ │ ├── META-INF │ │ └── services │ │ │ └── com.google.devtools.ksp.processing.SymbolProcessorProvider │ │ └── ktorm-ksp-compiler │ │ └── .editorconfig │ └── test │ ├── kotlin │ └── org │ │ └── ktorm │ │ └── ksp │ │ └── compiler │ │ ├── BaseKspTest.kt │ │ ├── generator │ │ ├── AddFunctionGeneratorTest.kt │ │ ├── ComponentFunctionGeneratorTest.kt │ │ ├── CopyFunctionGeneratorTest.kt │ │ ├── EntitySequencePropertyGeneratorTest.kt │ │ ├── ExtCodeGeneratorTest.kt │ │ ├── PseudoConstructorFunctionGeneratorTest.kt │ │ ├── RefsClassGeneratorTest.kt │ │ ├── TableClassGeneratorTest.kt │ │ └── UpdateFunctionGeneratorTest.kt │ │ └── parser │ │ ├── CompoundPrimaryKeysTest.kt │ │ ├── DatabaseNamingStrategyTest.kt │ │ ├── MetadataParserTest.kt │ │ └── SqlTypeTest.kt │ └── resources │ ├── META-INF │ └── services │ │ └── org.ktorm.ksp.spi.ExtCodeGenerator │ ├── drop-ksp-data.sql │ ├── init-ksp-data.sql │ └── simplelogger.properties ├── ktorm-ksp-spi ├── ktorm-ksp-spi.gradle.kts └── src │ └── main │ └── kotlin │ └── org │ └── ktorm │ └── ksp │ └── spi │ ├── CodingNamingStrategy.kt │ ├── ColumnMetadata.kt │ ├── DatabaseNamingStrategy.kt │ ├── ExtCodeGenerator.kt │ └── TableMetadata.kt ├── ktorm-support-mysql ├── ktorm-support-mysql.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── ktorm │ │ │ └── support │ │ │ └── mysql │ │ │ ├── BulkInsert.kt │ │ │ ├── DefaultValue.kt │ │ │ ├── Functions.kt │ │ │ ├── Global.kt │ │ │ ├── InsertOrUpdate.kt │ │ │ ├── Lock.kt │ │ │ ├── MatchAgainst.kt │ │ │ ├── MySqlDialect.kt │ │ │ ├── MySqlExpressionVisitor.kt │ │ │ ├── MySqlFormatter.kt │ │ │ └── NaturalJoin.kt │ ├── moditect │ │ └── module-info.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.ktorm.database.SqlDialect │ └── test │ ├── kotlin │ └── org │ │ └── ktorm │ │ └── support │ │ └── mysql │ │ ├── BaseMySqlTest.kt │ │ ├── CommonTest.kt │ │ ├── DefaultValueTest.kt │ │ ├── QueryTest.kt │ │ └── WindowFunctionTest.kt │ └── resources │ ├── drop-mysql-data.sql │ └── init-mysql-data.sql ├── ktorm-support-oracle ├── ktorm-support-oracle.gradle.kts ├── lib │ └── ojdbc6-11.2.0.3.jar └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── ktorm │ │ │ └── support │ │ │ └── oracle │ │ │ ├── OracleDialect.kt │ │ │ └── OracleFormatter.kt │ ├── moditect │ │ └── module-info.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.ktorm.database.SqlDialect │ └── test │ ├── kotlin │ └── org │ │ └── ktorm │ │ └── support │ │ └── oracle │ │ ├── BaseOracleTest.kt │ │ ├── CommonTest.kt │ │ ├── QueryTest.kt │ │ ├── WindowFunctionTest0.kt │ │ ├── WindowFunctionTest1.kt │ │ └── WindowFunctionTest2.kt │ └── resources │ ├── drop-oracle-data.sql │ └── init-oracle-data.sql ├── ktorm-support-postgresql ├── ktorm-support-postgresql.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── ktorm │ │ │ └── support │ │ │ └── postgresql │ │ │ ├── BulkInsert.kt │ │ │ ├── DefaultValue.kt │ │ │ ├── EarthDistance.kt │ │ │ ├── Functions.kt │ │ │ ├── Global.kt │ │ │ ├── HStore.kt │ │ │ ├── ILike.kt │ │ │ ├── InsertOrUpdate.kt │ │ │ ├── Lock.kt │ │ │ ├── PostgreSqlDialect.kt │ │ │ ├── PostgreSqlExpressionVisitor.kt │ │ │ ├── PostgreSqlFormatter.kt │ │ │ └── SqlTypes.kt │ ├── moditect │ │ └── module-info.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.ktorm.database.SqlDialect │ └── test │ ├── kotlin │ └── org │ │ └── ktorm │ │ └── support │ │ └── postgresql │ │ ├── ArraysTest.kt │ │ ├── BasePostgreSqlTest.kt │ │ ├── CommonTest.kt │ │ ├── ConnectionPoolTest.kt │ │ ├── DefaultValueTest.kt │ │ ├── DmlTest.kt │ │ ├── EarthdistanceTest.kt │ │ ├── FunctionsTest.kt │ │ ├── HStoreTest.kt │ │ ├── QueryTest.kt │ │ └── WindowFunctionTest.kt │ └── resources │ ├── drop-postgresql-data.sql │ └── init-postgresql-data.sql ├── ktorm-support-sqlite ├── ktorm-support-sqlite.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── ktorm │ │ │ └── support │ │ │ └── sqlite │ │ │ ├── BulkInsert.kt │ │ │ ├── Functions.kt │ │ │ ├── InsertOrUpdate.kt │ │ │ ├── SQLiteDialect.kt │ │ │ ├── SQLiteExpressionVisitor.kt │ │ │ └── SQLiteFormatter.kt │ ├── moditect │ │ └── module-info.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.ktorm.database.SqlDialect │ └── test │ ├── kotlin │ └── org │ │ └── ktorm │ │ └── support │ │ └── sqlite │ │ ├── BaseSQLiteTest.kt │ │ ├── CommonTest.kt │ │ ├── DmlTest.kt │ │ ├── QueryTest.kt │ │ └── WindowFunctionTest.kt │ └── resources │ ├── drop-sqlite-data.sql │ └── init-sqlite-data.sql ├── ktorm-support-sqlserver ├── ktorm-support-sqlserver.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── ktorm │ │ │ └── support │ │ │ └── sqlserver │ │ │ ├── SqlServerDialect.kt │ │ │ ├── SqlServerFormatter.kt │ │ │ └── SqlTypes.kt │ ├── moditect │ │ └── module-info.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.ktorm.database.SqlDialect │ └── test │ ├── kotlin │ └── org │ │ └── ktorm │ │ └── support │ │ └── sqlserver │ │ ├── BaseSqlServerTest.kt │ │ ├── CommonTest.kt │ │ ├── QueryTest.kt │ │ └── WindowFunctionTest.kt │ └── resources │ ├── container-license-acceptance.txt │ ├── drop-sqlserver-data.sql │ └── init-sqlserver-data.sql ├── ktorm.version └── settings.gradle.kts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.ktorm.org/en/sponsor.html'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | !gradle/wrapper/gradle-wrapper.jar 3 | build/ 4 | out/ 5 | logs/ 6 | .DS_Store 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | nbproject/private/ 24 | nbbuild/ 25 | dist/ 26 | nbdist/ 27 | .nb-gradle/ 28 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to make participation in our project and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, sex characteristics, gender identity and expression, 10 | level of experience, education, socio-economic status, nationality, personal 11 | appearance, race, religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or 42 | reject comments, commits, code, wiki edits, issues, and other contributions 43 | that are not aligned to this Code of Conduct, or to ban temporarily or 44 | permanently any contributor for other behaviors that they deem inappropriate, 45 | threatening, offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies within all project spaces, and it also applies when 50 | an individual is representing the project or its community in public spaces. 51 | Examples of representing a project or community include using an official 52 | project e-mail address, posting via an official social media account, or acting 53 | as an appointed representative at an online or offline event. Representation of 54 | a project may be further defined and clarified by project maintainers. 55 | 56 | ## Enforcement 57 | 58 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 59 | reported by contacting the project team at me@liuwj.me. All 60 | complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | 74 | [homepage]: https://www.contributor-covenant.org 75 | 76 | For answers to common questions about this code of conduct, see 77 | https://www.contributor-covenant.org/faq 78 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing 3 | 4 | First off, thank you for considering contributing to this project. It's people like you that make Ktorm such a great framework. 5 | 6 | Pull requests are always welcome and can be a quick way to get your fix or improvement slated for the next release. Before creating your PR, please note that: 7 | 8 | - By contributing to Ktorm, you agree to uphold our [Code of Conduct](CODE_OF_CONDUCT.md). 9 | - By contributing to Ktorm, you agree that your contributions will be licensed under [Apache License 2.0](LICENSE). 10 | - Coding Conventions are very important. Refer to the [Kotlin Style Guide](https://kotlinlang.org/docs/reference/coding-conventions.html) for the recommended coding standards of Ktorm. 11 | - If you've added code that should be tested, add tests and ensure they all pass. If you've changed APIs, update the documentation. 12 | - If it's your first time contributing to Ktorm, please also update [this file](buildSrc/src/main/kotlin/ktorm.publish.gradle.kts), add your GitHub ID to the developer's info, which will let more people know your contributions. 13 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | group = "org.ktorm" 3 | version = file("ktorm.version").readLines()[0] 4 | 5 | plugins { 6 | id("ktorm.dokka") 7 | } 8 | 9 | repositories { 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | `kotlin-dsl` 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | gradlePluginPortal() 9 | } 10 | 11 | dependencies { 12 | api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") 13 | api("org.jetbrains.dokka:dokka-gradle-plugin:1.9.20") 14 | api("org.jetbrains.dokka:dokka-base:1.9.20") 15 | api("org.moditect:moditect:1.0.0.RC1") 16 | api("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.6") 17 | } 18 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/ktorm.base.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | group = rootProject.group 3 | version = rootProject.version 4 | 5 | plugins { 6 | id("kotlin") 7 | id("org.gradle.jacoco") 8 | id("io.gitlab.arturbosch.detekt") 9 | } 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | api(kotlin("stdlib")) 17 | api(kotlin("reflect")) 18 | testImplementation(kotlin("test-junit")) 19 | detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:${detekt.toolVersion}") 20 | } 21 | 22 | detekt { 23 | source.setFrom("src/main/kotlin") 24 | config.setFrom("${project.rootDir}/detekt.yml") 25 | } 26 | 27 | java { 28 | sourceCompatibility = JavaVersion.VERSION_1_8 29 | targetCompatibility = JavaVersion.VERSION_1_8 30 | } 31 | 32 | tasks { 33 | // Lifecycle task for code generation. 34 | val codegen by registering { /* do nothing */ } 35 | 36 | compileKotlin { 37 | dependsOn(codegen) 38 | 39 | kotlinOptions { 40 | jvmTarget = "1.8" 41 | allWarningsAsErrors = true 42 | freeCompilerArgs = listOf("-Xexplicit-api=strict") 43 | } 44 | } 45 | 46 | compileTestKotlin { 47 | kotlinOptions { 48 | jvmTarget = "1.8" 49 | } 50 | } 51 | 52 | jacocoTestReport { 53 | reports { 54 | csv.required.set(true) 55 | xml.required.set(true) 56 | html.required.set(true) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/ktorm.dokka.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("org.jetbrains.dokka") 4 | } 5 | 6 | tasks.named("dokkaHtmlMultiModule") { 7 | val tmplDir = System.getProperty("dokka.templatesDir") 8 | if (!tmplDir.isNullOrEmpty()) { 9 | pluginConfiguration { 10 | templatesDir = File(tmplDir) 11 | } 12 | } 13 | } 14 | 15 | subprojects { 16 | apply(plugin = "org.jetbrains.dokka") 17 | 18 | tasks.dokkaJavadoc { 19 | dependsOn("codegen") 20 | 21 | dokkaSourceSets.named("main") { 22 | suppressGeneratedFiles.set(false) 23 | } 24 | } 25 | 26 | tasks.named("dokkaHtmlPartial") { 27 | dependsOn("codegen") 28 | 29 | val tmplDir = System.getProperty("dokka.templatesDir") 30 | if (!tmplDir.isNullOrEmpty()) { 31 | pluginConfiguration { 32 | templatesDir = File(tmplDir) 33 | } 34 | } 35 | 36 | dokkaSourceSets.named("main") { 37 | suppressGeneratedFiles.set(false) 38 | sourceLink { 39 | localDirectory.set(file("src/main/kotlin")) 40 | remoteUrl.set(java.net.URL("https://github.com/kotlin-orm/ktorm/blob/master/${project.name}/src/main/kotlin")) 41 | remoteLineSuffix.set("#L") 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/ktorm.modularity.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("kotlin") 4 | } 5 | 6 | val moditect by tasks.registering { 7 | doLast { 8 | // Generate a multi-release modulized jar, module descriptor position: META-INF/versions/9/module-info.class 9 | val inputJar = tasks.jar.flatMap { it.archiveFile }.map { it.asFile.toPath() }.get() 10 | val outputDir = file("build/moditect").apply { mkdirs() }.toPath() 11 | val moduleInfo = file("src/main/moditect/module-info.java").readText() 12 | val version = project.version.toString() 13 | org.moditect.commands.AddModuleInfo(moduleInfo, null, version, inputJar, outputDir, "9", true).run() 14 | 15 | // Replace the original jar with the modulized jar. 16 | copy { 17 | from(outputDir.resolve(inputJar.fileName)) 18 | into(inputJar.parent) 19 | } 20 | } 21 | } 22 | 23 | tasks { 24 | moditect { 25 | dependsOn(jar) 26 | } 27 | jar { 28 | finalizedBy(moditect) 29 | } 30 | } 31 | 32 | if (JavaVersion.current() >= JavaVersion.VERSION_1_9) { 33 | // Let kotlin compiler know the module descriptor. 34 | sourceSets.main { 35 | kotlin.srcDir("src/main/moditect") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/ktorm.source-header-check.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("kotlin") 4 | } 5 | 6 | val licenseHeaderText = """ 7 | /* 8 | * Copyright 2018-2024 the original author or authors. 9 | * 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | */ 22 | """.trimIndent() 23 | 24 | val checkSourceHeader by tasks.registering { 25 | doLast { 26 | val sources = sourceSets.main.get() 27 | 28 | for (dir in sources.allSource.srcDirs) { 29 | val tree = fileTree(dir) 30 | tree.include("**/*.kt") 31 | 32 | tree.visit { 33 | if (file.isFile && !file.readText().startsWith(licenseHeaderText)) { 34 | throw IllegalStateException("Copyright header not found in file: $file") 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | tasks.check { 42 | dependsOn(checkSourceHeader) 43 | } 44 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kotlin-orm/ktorm/601d9a11f45f33bb3d8f637162f6650236a84e89/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /ktorm-core/ktorm-core.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.modularity") 5 | id("ktorm.publish") 6 | id("ktorm.source-header-check") 7 | id("ktorm.tuples-codegen") 8 | } 9 | 10 | dependencies { 11 | compileOnly("org.springframework:spring-jdbc:5.0.10.RELEASE") 12 | compileOnly("org.springframework:spring-tx:5.0.10.RELEASE") 13 | testImplementation("com.h2database:h2:1.4.198") 14 | testImplementation("org.slf4j:slf4j-simple:2.0.3") 15 | } 16 | 17 | val testOutput by configurations.creating { 18 | extendsFrom(configurations["testImplementation"]) 19 | } 20 | 21 | val testJar by tasks.registering(Jar::class) { 22 | dependsOn(tasks.testClasses) 23 | from(sourceSets.test.map { it.output }) 24 | archiveClassifier.set("test") 25 | } 26 | 27 | artifacts { 28 | add(testOutput.name, testJar) 29 | } 30 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/database/JdbcExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.database 18 | 19 | import org.ktorm.expression.ArgumentExpression 20 | import org.ktorm.schema.SqlType 21 | import java.sql.PreparedStatement 22 | import java.sql.ResultSet 23 | import kotlin.contracts.ExperimentalContracts 24 | import kotlin.contracts.InvocationKind 25 | import kotlin.contracts.contract 26 | 27 | /** 28 | * Execute the given [block] function on this resource and then close it down correctly whether an exception 29 | * is thrown or not. 30 | * 31 | * @param block a function to process this [AutoCloseable] resource. 32 | * @return the result of [block] function invoked on this resource. 33 | */ 34 | @OptIn(ExperimentalContracts::class) 35 | @Suppress("ConvertTryFinallyToUseCall") 36 | public inline fun T.use(block: (T) -> R): R { 37 | contract { 38 | callsInPlace(block, InvocationKind.EXACTLY_ONCE) 39 | } 40 | 41 | try { 42 | return block(this) 43 | } finally { 44 | this?.close() 45 | } 46 | } 47 | 48 | /** 49 | * Set the arguments for this [PreparedStatement]. 50 | * 51 | * @since 2.7 52 | * @param args the arguments to set into the statement. 53 | */ 54 | public fun PreparedStatement.setArguments(args: List>) { 55 | for ((i, expr) in args.withIndex()) { 56 | @Suppress("UNCHECKED_CAST") 57 | val sqlType = expr.sqlType as SqlType 58 | sqlType.setParameter(this, i + 1, expr.value) 59 | } 60 | } 61 | 62 | /** 63 | * Return an iterator over the rows of this [ResultSet]. 64 | * 65 | * The returned iterator just wraps the [ResultSet.next] method and every element returned by the iterator is 66 | * exactly the same reference as the this [ResultSet]. 67 | */ 68 | @Suppress("IteratorHasNextCallsNextMethod") 69 | public operator fun T.iterator(): Iterator = object : Iterator { 70 | private val rs = this@iterator 71 | private var hasNext: Boolean? = null 72 | 73 | override fun hasNext(): Boolean { 74 | return hasNext ?: rs.next().also { hasNext = it } 75 | } 76 | 77 | override fun next(): T { 78 | return if (hasNext()) rs.also { hasNext = null } else throw NoSuchElementException() 79 | } 80 | } 81 | 82 | /** 83 | * Wrap this [ResultSet] as [Iterable]. 84 | * 85 | * This function is useful when we want to iterate a result set by a for-each loop, or process it via extension 86 | * functions of Kotlin standard lib, such as [Iterable.map], [Iterable.flatMap], etc. 87 | * 88 | * @see ResultSet.iterator 89 | */ 90 | public fun T.asIterable(): Iterable { 91 | return Iterable { iterator() } 92 | } 93 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/database/SpringManagedTransactionManager.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.database 18 | 19 | import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy 20 | import org.springframework.transaction.annotation.Transactional 21 | import java.sql.Connection 22 | import javax.sql.DataSource 23 | 24 | /** 25 | * [TransactionManager] implementation that delegates all transactions to the Spring framework. 26 | * 27 | * This class enables the Spring support, and it's used by [Database] instances created 28 | * by [Database.connectWithSpringSupport] function. Once the Spring support enabled, the 29 | * transaction management will be delegated to the Spring framework, so the [Database.useTransaction] 30 | * function is not available anymore, applications should use Spring's [Transactional] annotation instead. 31 | * 32 | * @property dataSource the data source used to obtained connections, typically comes from Spring's application context. 33 | */ 34 | public class SpringManagedTransactionManager(public val dataSource: DataSource) : TransactionManager { 35 | private val proxy = dataSource as? TransactionAwareDataSourceProxy ?: TransactionAwareDataSourceProxy(dataSource) 36 | 37 | override val defaultIsolation: TransactionIsolation? = null 38 | 39 | override val currentTransaction: Transaction? = null 40 | 41 | override fun newTransaction(isolation: TransactionIsolation?): Nothing { 42 | val msg = "Transaction is managed by Spring, please use Spring's @Transactional annotation instead." 43 | throw UnsupportedOperationException(msg) 44 | } 45 | 46 | override fun newConnection(): Connection { 47 | return proxy.connection 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/dsl/Aggregation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.dsl 18 | 19 | import org.ktorm.expression.AggregateExpression 20 | import org.ktorm.expression.AggregateType 21 | import org.ktorm.schema.* 22 | 23 | /** 24 | * The min function, translated to `min(column)` in SQL. 25 | */ 26 | public fun > min(column: ColumnDeclaring): AggregateExpression { 27 | return AggregateExpression(AggregateType.MIN, column.asExpression(), false, column.sqlType) 28 | } 29 | 30 | /** 31 | * The min function with distinct, translated to `min(distinct column)` in SQL. 32 | */ 33 | public fun > minDistinct(column: ColumnDeclaring): AggregateExpression { 34 | return AggregateExpression(AggregateType.MIN, column.asExpression(), true, column.sqlType) 35 | } 36 | 37 | /** 38 | * The max function, translated to `max(column)` in SQL. 39 | */ 40 | public fun > max(column: ColumnDeclaring): AggregateExpression { 41 | return AggregateExpression(AggregateType.MAX, column.asExpression(), false, column.sqlType) 42 | } 43 | 44 | /** 45 | * The max function with distinct, translated to `max(distinct column)` in SQL. 46 | */ 47 | public fun > maxDistinct(column: ColumnDeclaring): AggregateExpression { 48 | return AggregateExpression(AggregateType.MAX, column.asExpression(), true, column.sqlType) 49 | } 50 | 51 | /** 52 | * The avg function, translated to `avg(column)` in SQL. 53 | */ 54 | public fun avg(column: ColumnDeclaring): AggregateExpression { 55 | return AggregateExpression(AggregateType.AVG, column.asExpression(), false, DoubleSqlType) 56 | } 57 | 58 | /** 59 | * The avg function with distinct, translated to `avg(distinct column)` in SQL. 60 | */ 61 | public fun avgDistinct(column: ColumnDeclaring): AggregateExpression { 62 | return AggregateExpression(AggregateType.AVG, column.asExpression(), true, DoubleSqlType) 63 | } 64 | 65 | /** 66 | * The sum function, translated to `sum(column)` in SQL. 67 | */ 68 | public fun sum(column: ColumnDeclaring): AggregateExpression { 69 | return AggregateExpression(AggregateType.SUM, column.asExpression(), false, column.sqlType) 70 | } 71 | 72 | /** 73 | * The sum function with distinct, translated to `sum(distinct column)` in SQL. 74 | */ 75 | public fun sumDistinct(column: ColumnDeclaring): AggregateExpression { 76 | return AggregateExpression(AggregateType.SUM, column.asExpression(), true, column.sqlType) 77 | } 78 | 79 | /** 80 | * The count function, translated to `count(column)` in SQL. 81 | */ 82 | public fun count(column: ColumnDeclaring<*>? = null): AggregateExpression { 83 | return AggregateExpression(AggregateType.COUNT, column?.asExpression(), false, IntSqlType) 84 | } 85 | 86 | /** 87 | * The count function with distinct, translated to `count(distinct column)` in SQL. 88 | */ 89 | public fun countDistinct(column: ColumnDeclaring<*>? = null): AggregateExpression { 90 | return AggregateExpression(AggregateType.COUNT, column?.asExpression(), true, IntSqlType) 91 | } 92 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/dsl/CountExpression.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.dsl 18 | 19 | import org.ktorm.database.Database 20 | import org.ktorm.expression.* 21 | 22 | internal fun Database.toCountExpression(expr: QueryExpression): SelectExpression { 23 | val expression = dialect.createExpressionVisitor(OrderByRemover(this)).visit(expr) as QueryExpression 24 | val count = count().aliased(null) 25 | 26 | if (expression is SelectExpression && expression.isSimpleSelect()) { 27 | return expression.copy(columns = listOf(count), offset = null, limit = null) 28 | } else { 29 | return SelectExpression( 30 | columns = listOf(count), 31 | from = when (expression) { 32 | is SelectExpression -> expression.copy(offset = null, limit = null, tableAlias = "tmp_count") 33 | is UnionExpression -> expression.copy(offset = null, limit = null, tableAlias = "tmp_count") 34 | } 35 | ) 36 | } 37 | } 38 | 39 | private fun SelectExpression.isSimpleSelect(): Boolean { 40 | if (groupBy.isNotEmpty()) { 41 | return false 42 | } 43 | if (isDistinct) { 44 | return false 45 | } 46 | return columns.all { it.expression is ColumnExpression } 47 | } 48 | 49 | private class OrderByRemover(val database: Database) : SqlExpressionVisitorInterceptor { 50 | 51 | override fun intercept(expr: SqlExpression, visitor: SqlExpressionVisitor): SqlExpression? { 52 | if (expr is SelectExpression) { 53 | if (expr.orderBy.any { database.hasArgument(it) }) { 54 | return expr 55 | } else { 56 | return expr.copy(orderBy = emptyList()) 57 | } 58 | } 59 | 60 | if (expr is UnionExpression) { 61 | if (expr.orderBy.any { database.hasArgument(it) }) { 62 | return expr 63 | } else { 64 | return expr.copy(orderBy = emptyList()) 65 | } 66 | } 67 | 68 | return null 69 | } 70 | } 71 | 72 | private fun Database.hasArgument(expr: SqlExpression): Boolean { 73 | var hasArgument = false 74 | 75 | val interceptor = object : SqlExpressionVisitorInterceptor { 76 | override fun intercept(expr: SqlExpression, visitor: SqlExpressionVisitor): SqlExpression? { 77 | if (expr is ArgumentExpression<*>) { 78 | hasArgument = true 79 | } 80 | 81 | return null 82 | } 83 | } 84 | 85 | dialect.createExpressionVisitor(interceptor).visit(expr) 86 | return hasArgument 87 | } 88 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensionsApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.entity 18 | 19 | import org.ktorm.schema.ColumnBinding 20 | 21 | /** 22 | * Entity extension APIs. 23 | * 24 | * Note these APIs are designed to be used by Ktorm's 3rd party extensions, applications should not use them directly. 25 | * 26 | * @since 3.5.0 27 | */ 28 | public class EntityExtensionsApi { 29 | 30 | /** 31 | * Check if the specific column value exists in this entity. 32 | * 33 | * Please keep in mind that null is also a valid column value, so if a column value was set to null, this function 34 | * returns true. 35 | */ 36 | public fun Entity<*>.hasColumnValue(binding: ColumnBinding): Boolean { 37 | return implementation.hasColumnValue(binding) 38 | } 39 | 40 | /** 41 | * Get the specific column value from this entity, returning null if the value doesn't exist. 42 | */ 43 | public fun Entity<*>.getColumnValue(binding: ColumnBinding): Any? { 44 | return implementation.getColumnValue(binding) 45 | } 46 | 47 | /** 48 | * Set the specific column's value into this entity. 49 | */ 50 | public fun Entity<*>.setColumnValue(binding: ColumnBinding, value: Any?) { 51 | implementation.setColumnValue(binding, value) 52 | } 53 | 54 | /** 55 | * Check if this entity is attached to the database. 56 | * 57 | * @since 4.1.0 58 | */ 59 | public fun Entity<*>.isAttached(): Boolean { 60 | val impl = this.implementation 61 | return impl.fromDatabase != null && impl.fromTable != null && impl.parent == null 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressionVisitorInterceptor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.expression 18 | 19 | import org.ktorm.entity.DefaultMethodHandler 20 | import java.lang.reflect.InvocationHandler 21 | import java.lang.reflect.Method 22 | import java.lang.reflect.Proxy 23 | import kotlin.reflect.KClass 24 | 25 | /** 26 | * Interceptor that can intercept the visit functions for [SqlExpressionVisitor] and its sub-interfaces. 27 | * 28 | * @since 3.6.0 29 | */ 30 | public interface SqlExpressionVisitorInterceptor { 31 | 32 | /** 33 | * Intercept the visit functions. 34 | * 35 | * If a non-null result is returned, this result will be used as the visit result, the origin visit function 36 | * will be skipped. Otherwise, if null is returned, the origin visit function will be executed, because null 37 | * value means that we don't want to intercept the logic. 38 | */ 39 | public fun intercept(expr: SqlExpression, visitor: SqlExpressionVisitor): SqlExpression? 40 | } 41 | 42 | /** 43 | * Create a default visitor instance for [this] interface using the specific [interceptor]. 44 | * 45 | * @since 3.6.0 46 | */ 47 | @Suppress("UNCHECKED_CAST") 48 | public fun KClass.newVisitorInstance(interceptor: SqlExpressionVisitorInterceptor): T { 49 | val c = this.java 50 | if (!c.isInterface) { 51 | throw IllegalArgumentException("${c.name} is not an interface.") 52 | } 53 | if (this.members.any { it.isAbstract }) { 54 | throw IllegalArgumentException("${c.name} cannot have any abstract members.") 55 | } 56 | 57 | return Proxy.newProxyInstance(c.classLoader, arrayOf(c), VisitorInvocationHandler(interceptor)) as T 58 | } 59 | 60 | /** 61 | * Visitor invocation handler with intercepting ability. 62 | */ 63 | private class VisitorInvocationHandler(val interceptor: SqlExpressionVisitorInterceptor) : InvocationHandler { 64 | 65 | override fun invoke(proxy: Any, method: Method, args: Array?): Any? { 66 | if (method.declaringClass.kotlin == Any::class) { 67 | return when (method.name) { 68 | "equals" -> proxy === args!![0] 69 | "hashCode" -> System.identityHashCode(proxy) 70 | "toString" -> "Proxy\$${proxy.javaClass.interfaces[0].simpleName}(interceptor=$interceptor)" 71 | else -> throw IllegalStateException("Unrecognized method: $method") 72 | } 73 | } 74 | 75 | if (canIntercept(method)) { 76 | val r = interceptor.intercept(args!![0] as SqlExpression, proxy as SqlExpressionVisitor) 77 | if (r != null) { 78 | return r 79 | } 80 | } 81 | 82 | return DefaultMethodHandler.forMethod(method).invoke(proxy, args) 83 | } 84 | 85 | private fun canIntercept(method: Method): Boolean { 86 | return method.name.startsWith("visit") 87 | && method.parameterCount == 1 88 | && method.parameterTypes[0] == method.returnType 89 | && SqlExpression::class.java.isAssignableFrom(method.returnType) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/logging/AndroidLoggerAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.logging 18 | 19 | import org.ktorm.entity.invoke0 20 | 21 | /** 22 | * Adapter [Logger] implementation integrating 23 | * [android.util.Log](https://developer.android.com/reference/android/util/Log) with Ktorm. 24 | */ 25 | public class AndroidLoggerAdapter(private val tag: String) : Logger { 26 | // Access Android Log API by reflection, because Android SDK is not a JDK 9 module, 27 | // we are not able to require it in module-info.java. 28 | private val logClass = Class.forName("android.util.Log") 29 | private val isLoggableMethod = logClass.getMethod("isLoggable", String::class.java, Int::class.javaPrimitiveType) 30 | private val vMethod = logClass.getMethod("v", String::class.java, String::class.java, Throwable::class.java) 31 | private val dMethod = logClass.getMethod("d", String::class.java, String::class.java, Throwable::class.java) 32 | private val iMethod = logClass.getMethod("i", String::class.java, String::class.java, Throwable::class.java) 33 | private val wMethod = logClass.getMethod("w", String::class.java, String::class.java, Throwable::class.java) 34 | private val eMethod = logClass.getMethod("e", String::class.java, String::class.java, Throwable::class.java) 35 | 36 | private object Levels { 37 | const val VERBOSE = 2 38 | const val DEBUG = 3 39 | const val INFO = 4 40 | const val WARN = 5 41 | const val ERROR = 6 42 | } 43 | 44 | override fun isTraceEnabled(): Boolean { 45 | return isLoggableMethod.invoke0(null, tag, Levels.VERBOSE) as Boolean 46 | } 47 | 48 | override fun trace(msg: String, e: Throwable?) { 49 | vMethod.invoke0(null, tag, msg, e) 50 | } 51 | 52 | override fun isDebugEnabled(): Boolean { 53 | return isLoggableMethod.invoke0(null, tag, Levels.DEBUG) as Boolean 54 | } 55 | 56 | override fun debug(msg: String, e: Throwable?) { 57 | dMethod.invoke0(null, tag, msg, e) 58 | } 59 | 60 | override fun isInfoEnabled(): Boolean { 61 | return isLoggableMethod.invoke0(null, tag, Levels.INFO) as Boolean 62 | } 63 | 64 | override fun info(msg: String, e: Throwable?) { 65 | iMethod.invoke0(null, tag, msg, e) 66 | } 67 | 68 | override fun isWarnEnabled(): Boolean { 69 | return isLoggableMethod.invoke0(null, tag, Levels.WARN) as Boolean 70 | } 71 | 72 | override fun warn(msg: String, e: Throwable?) { 73 | wMethod.invoke0(null, tag, msg, e) 74 | } 75 | 76 | override fun isErrorEnabled(): Boolean { 77 | return isLoggableMethod.invoke0(null, tag, Levels.ERROR) as Boolean 78 | } 79 | 80 | override fun error(msg: String, e: Throwable?) { 81 | eMethod.invoke0(null, tag, msg, e) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/logging/CommonsLoggerAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.logging 18 | 19 | import org.ktorm.entity.invoke0 20 | 21 | /** 22 | * Adapter [Logger] implementation integrating Apache Commons Logging with Ktorm. 23 | */ 24 | public class CommonsLoggerAdapter(loggerName: String) : Logger { 25 | // Access commons logging API by reflection, because it is not a JDK 9 module, 26 | // we are not able to require it in module-info.java. 27 | private val logFactoryClass = Class.forName("org.apache.commons.logging.LogFactory") 28 | private val logClass = Class.forName("org.apache.commons.logging.Log") 29 | private val getLogMethod = logFactoryClass.getMethod("getLog", String::class.java) 30 | private val isTraceEnabledMethod = logClass.getMethod("isTraceEnabled") 31 | private val isDebugEnabledMethod = logClass.getMethod("isDebugEnabled") 32 | private val isInfoEnabledMethod = logClass.getMethod("isInfoEnabled") 33 | private val isWarnEnabledMethod = logClass.getMethod("isWarnEnabled") 34 | private val isErrorEnabledMethod = logClass.getMethod("isErrorEnabled") 35 | private val traceMethod = logClass.getMethod("trace", Any::class.java, Throwable::class.java) 36 | private val debugMethod = logClass.getMethod("debug", Any::class.java, Throwable::class.java) 37 | private val infoMethod = logClass.getMethod("info", Any::class.java, Throwable::class.java) 38 | private val warnMethod = logClass.getMethod("warn", Any::class.java, Throwable::class.java) 39 | private val errorMethod = logClass.getMethod("error", Any::class.java, Throwable::class.java) 40 | private val logger = getLogMethod.invoke0(null, loggerName) 41 | 42 | override fun isTraceEnabled(): Boolean { 43 | return isTraceEnabledMethod.invoke0(logger) as Boolean 44 | } 45 | 46 | override fun trace(msg: String, e: Throwable?) { 47 | traceMethod.invoke0(logger, msg, e) 48 | } 49 | 50 | override fun isDebugEnabled(): Boolean { 51 | return isDebugEnabledMethod.invoke0(logger) as Boolean 52 | } 53 | 54 | override fun debug(msg: String, e: Throwable?) { 55 | debugMethod.invoke0(logger, msg, e) 56 | } 57 | 58 | override fun isInfoEnabled(): Boolean { 59 | return isInfoEnabledMethod.invoke0(logger) as Boolean 60 | } 61 | 62 | override fun info(msg: String, e: Throwable?) { 63 | infoMethod.invoke0(logger, msg, e) 64 | } 65 | 66 | override fun isWarnEnabled(): Boolean { 67 | return isWarnEnabledMethod.invoke0(logger) as Boolean 68 | } 69 | 70 | override fun warn(msg: String, e: Throwable?) { 71 | warnMethod.invoke0(logger, msg, e) 72 | } 73 | 74 | override fun isErrorEnabled(): Boolean { 75 | return isErrorEnabledMethod.invoke0(logger) as Boolean 76 | } 77 | 78 | override fun error(msg: String, e: Throwable?) { 79 | errorMethod.invoke0(logger, msg, e) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/logging/ConsoleLogger.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.logging 18 | 19 | /** 20 | * Simple [Logger] implementation printing logs to the console. While messages at WARN or ERROR levels are printed to 21 | * [System.err], others are printed to [System.out]. 22 | * 23 | * @property threshold a threshold controlling which log levels are enabled. 24 | */ 25 | public class ConsoleLogger(public val threshold: LogLevel) : Logger { 26 | 27 | override fun isTraceEnabled(): Boolean { 28 | return LogLevel.TRACE >= threshold 29 | } 30 | 31 | override fun trace(msg: String, e: Throwable?) { 32 | log(LogLevel.TRACE, msg, e) 33 | } 34 | 35 | override fun isDebugEnabled(): Boolean { 36 | return LogLevel.DEBUG >= threshold 37 | } 38 | 39 | override fun debug(msg: String, e: Throwable?) { 40 | log(LogLevel.DEBUG, msg, e) 41 | } 42 | 43 | override fun isInfoEnabled(): Boolean { 44 | return LogLevel.INFO >= threshold 45 | } 46 | 47 | override fun info(msg: String, e: Throwable?) { 48 | log(LogLevel.INFO, msg, e) 49 | } 50 | 51 | override fun isWarnEnabled(): Boolean { 52 | return LogLevel.WARN >= threshold 53 | } 54 | 55 | override fun warn(msg: String, e: Throwable?) { 56 | log(LogLevel.WARN, msg, e) 57 | } 58 | 59 | override fun isErrorEnabled(): Boolean { 60 | return LogLevel.ERROR >= threshold 61 | } 62 | 63 | override fun error(msg: String, e: Throwable?) { 64 | log(LogLevel.ERROR, msg, e) 65 | } 66 | 67 | private fun log(level: LogLevel, msg: String, e: Throwable?) { 68 | if (level >= threshold) { 69 | val out = if (level >= LogLevel.WARN) System.err else System.out 70 | out.println("[$level] $msg") 71 | 72 | if (e != null) { 73 | // Workaround for the compiler bug, see https://youtrack.jetbrains.com/issue/KT-34826 74 | @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "KotlinConstantConditions") 75 | (e as java.lang.Throwable).printStackTrace(out) 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/logging/JdkLoggerAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.logging 18 | 19 | import java.util.logging.Level 20 | 21 | /** 22 | * Adapter [Logger] implementation integrating [java.util.logging] with Ktorm. 23 | */ 24 | public class JdkLoggerAdapter(loggerName: String) : Logger { 25 | private val logger = java.util.logging.Logger.getLogger(loggerName) 26 | 27 | override fun isTraceEnabled(): Boolean { 28 | return logger.isLoggable(Level.FINEST) 29 | } 30 | 31 | override fun trace(msg: String, e: Throwable?) { 32 | logger.log(Level.FINEST, msg, e) 33 | } 34 | 35 | override fun isDebugEnabled(): Boolean { 36 | return logger.isLoggable(Level.FINE) 37 | } 38 | 39 | override fun debug(msg: String, e: Throwable?) { 40 | logger.log(Level.FINE, msg, e) 41 | } 42 | 43 | override fun isInfoEnabled(): Boolean { 44 | return logger.isLoggable(Level.INFO) 45 | } 46 | 47 | override fun info(msg: String, e: Throwable?) { 48 | logger.log(Level.INFO, msg, e) 49 | } 50 | 51 | override fun isWarnEnabled(): Boolean { 52 | return logger.isLoggable(Level.WARNING) 53 | } 54 | 55 | override fun warn(msg: String, e: Throwable?) { 56 | logger.log(Level.WARNING, msg, e) 57 | } 58 | 59 | override fun isErrorEnabled(): Boolean { 60 | return logger.isLoggable(Level.SEVERE) 61 | } 62 | 63 | override fun error(msg: String, e: Throwable?) { 64 | logger.log(Level.SEVERE, msg, e) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/logging/NoOpLogger.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.logging 18 | 19 | /** 20 | * [Logger] implementation that performs no operations. 21 | */ 22 | public object NoOpLogger : Logger { 23 | 24 | override fun isTraceEnabled(): Boolean { 25 | return false 26 | } 27 | 28 | override fun trace(msg: String, e: Throwable?) { 29 | // no-op 30 | } 31 | 32 | override fun isDebugEnabled(): Boolean { 33 | return false 34 | } 35 | 36 | override fun debug(msg: String, e: Throwable?) { 37 | // no-op 38 | } 39 | 40 | override fun isInfoEnabled(): Boolean { 41 | return false 42 | } 43 | 44 | override fun info(msg: String, e: Throwable?) { 45 | // no-op 46 | } 47 | 48 | override fun isWarnEnabled(): Boolean { 49 | return false 50 | } 51 | 52 | override fun warn(msg: String, e: Throwable?) { 53 | // no-op 54 | } 55 | 56 | override fun isErrorEnabled(): Boolean { 57 | return false 58 | } 59 | 60 | override fun error(msg: String, e: Throwable?) { 61 | // no-op 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/logging/Slf4jLoggerAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.logging 18 | 19 | import org.ktorm.entity.invoke0 20 | 21 | /** 22 | * Adapter [Logger] implementation integrating Slf4j with Ktorm. 23 | */ 24 | public class Slf4jLoggerAdapter(loggerName: String) : Logger { 25 | // Access SLF4J API by reflection, because we haven't required it in module-info.java. 26 | private val loggerFactoryClass = Class.forName("org.slf4j.LoggerFactory") 27 | private val loggerClass = Class.forName("org.slf4j.Logger") 28 | private val getLoggerMethod = loggerFactoryClass.getMethod("getLogger", String::class.java) 29 | private val isTraceEnabledMethod = loggerClass.getMethod("isTraceEnabled") 30 | private val isDebugEnabledMethod = loggerClass.getMethod("isDebugEnabled") 31 | private val isInfoEnabledMethod = loggerClass.getMethod("isInfoEnabled") 32 | private val isWarnEnabledMethod = loggerClass.getMethod("isWarnEnabled") 33 | private val isErrorEnabledMethod = loggerClass.getMethod("isErrorEnabled") 34 | private val traceMethod = loggerClass.getMethod("trace", String::class.java, Throwable::class.java) 35 | private val debugMethod = loggerClass.getMethod("debug", String::class.java, Throwable::class.java) 36 | private val infoMethod = loggerClass.getMethod("info", String::class.java, Throwable::class.java) 37 | private val warnMethod = loggerClass.getMethod("warn", String::class.java, Throwable::class.java) 38 | private val errorMethod = loggerClass.getMethod("error", String::class.java, Throwable::class.java) 39 | private val logger = getLoggerMethod.invoke0(null, loggerName) 40 | 41 | override fun isTraceEnabled(): Boolean { 42 | return isTraceEnabledMethod.invoke0(logger) as Boolean 43 | } 44 | 45 | override fun trace(msg: String, e: Throwable?) { 46 | traceMethod.invoke0(logger, msg, e) 47 | } 48 | 49 | override fun isDebugEnabled(): Boolean { 50 | return isDebugEnabledMethod.invoke0(logger) as Boolean 51 | } 52 | 53 | override fun debug(msg: String, e: Throwable?) { 54 | debugMethod.invoke0(logger, msg, e) 55 | } 56 | 57 | override fun isInfoEnabled(): Boolean { 58 | return isInfoEnabledMethod.invoke0(logger) as Boolean 59 | } 60 | 61 | override fun info(msg: String, e: Throwable?) { 62 | infoMethod.invoke0(logger, msg, e) 63 | } 64 | 65 | override fun isWarnEnabled(): Boolean { 66 | return isWarnEnabledMethod.invoke0(logger) as Boolean 67 | } 68 | 69 | override fun warn(msg: String, e: Throwable?) { 70 | warnMethod.invoke0(logger, msg, e) 71 | } 72 | 73 | override fun isErrorEnabled(): Boolean { 74 | return isErrorEnabledMethod.invoke0(logger) as Boolean 75 | } 76 | 77 | override fun error(msg: String, e: Throwable?) { 78 | errorMethod.invoke0(logger, msg, e) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/schema/ColumnBindingHandler.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.schema 18 | 19 | import org.ktorm.entity.Entity 20 | import org.ktorm.entity.defaultValue 21 | import org.ktorm.entity.kotlinProperty 22 | import java.lang.reflect.InvocationHandler 23 | import java.lang.reflect.Method 24 | import java.lang.reflect.Proxy 25 | import kotlin.reflect.KClass 26 | import kotlin.reflect.KProperty1 27 | import kotlin.reflect.full.isSubclassOf 28 | 29 | @PublishedApi 30 | internal class ColumnBindingHandler(val properties: MutableList>) : InvocationHandler { 31 | 32 | override fun invoke(proxy: Any, method: Method, args: Array?): Any? { 33 | when (method.declaringClass.kotlin) { 34 | Any::class, Entity::class -> { 35 | error("Unsupported method: $method") 36 | } 37 | else -> { 38 | val (prop, isGetter) = method.kotlinProperty ?: error("Unsupported method: $method") 39 | if (!prop.isAbstract) { 40 | error("Cannot bind a column to a non-abstract property: $prop") 41 | } 42 | if (!isGetter) { 43 | error("Cannot modify a property while we are binding a column to it, property: $prop") 44 | } 45 | 46 | properties += prop 47 | 48 | val returnType = method.returnType 49 | return when { 50 | returnType.kotlin.isSubclassOf(Entity::class) -> createProxy(returnType.kotlin, properties) 51 | returnType.isPrimitive -> returnType.defaultValue 52 | else -> null 53 | } 54 | } 55 | } 56 | } 57 | 58 | private fun error(msg: String): Nothing { 59 | throw UnsupportedOperationException(msg) 60 | } 61 | 62 | companion object { 63 | 64 | fun createProxy(entityClass: KClass<*>, properties: MutableList>): Entity<*> { 65 | val handler = ColumnBindingHandler(properties) 66 | return Proxy.newProxyInstance(entityClass.java.classLoader, arrayOf(entityClass.java), handler) as Entity<*> 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/schema/RefCounter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.schema 18 | 19 | /** 20 | * Created by vince at May 01, 2020. 21 | */ 22 | internal class RefCounter private constructor() { 23 | private var count = 0 24 | 25 | fun get(): Int { 26 | return count 27 | } 28 | 29 | fun getAndIncrement(): Int { 30 | return count++ 31 | } 32 | 33 | companion object { 34 | private val threadLocal = ThreadLocal() 35 | 36 | fun setContextCounter(counter: RefCounter) { 37 | if (threadLocal.get() != null) { 38 | throw IllegalStateException("The context counter is already set.") 39 | } 40 | 41 | threadLocal.set(counter) 42 | } 43 | 44 | fun getCounter(): RefCounter { 45 | val counter = threadLocal.get() ?: return RefCounter() 46 | threadLocal.remove() 47 | return counter 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ktorm-core/src/main/kotlin/org/ktorm/schema/TypeReference.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.schema 18 | 19 | import java.lang.reflect.ParameterizedType 20 | import java.lang.reflect.Type 21 | import kotlin.reflect.KClass 22 | import kotlin.reflect.KType 23 | import kotlin.reflect.jvm.jvmErasure 24 | 25 | /** 26 | * Base class used to obtain full generic type information by subclassing. 27 | */ 28 | @Suppress("UnnecessaryAbstractClass") 29 | public abstract class TypeReference { 30 | 31 | /** 32 | * The actual type argument of subclass without erased. 33 | */ 34 | public val referencedType: Type by lazy { findSuperclassTypeArgument(javaClass) } 35 | 36 | /** 37 | * The actual kotlin type argument of subclass without erased. 38 | */ 39 | public val referencedKotlinType: KType by lazy { findSuperclassTypeArgument(javaClass.kotlin) } 40 | 41 | private fun findSuperclassTypeArgument(cls: Class<*>): Type { 42 | val genericSuperclass = cls.genericSuperclass 43 | 44 | if (genericSuperclass is Class<*>) { 45 | if (genericSuperclass != TypeReference::class.java) { 46 | // Try to climb up the hierarchy until meet something useful. 47 | return findSuperclassTypeArgument(genericSuperclass.superclass) 48 | } else { 49 | throw IllegalStateException("Could not find the referenced type of class $javaClass") 50 | } 51 | } 52 | 53 | return (genericSuperclass as ParameterizedType).actualTypeArguments[0] 54 | } 55 | 56 | private fun findSuperclassTypeArgument(cls: KClass<*>): KType { 57 | val supertype = cls.supertypes.first { !it.jvmErasure.java.isInterface } 58 | 59 | if (supertype.arguments.isEmpty()) { 60 | if (supertype.jvmErasure != TypeReference::class) { 61 | // Try to climb up the hierarchy until meet something useful. 62 | return findSuperclassTypeArgument(supertype.jvmErasure) 63 | } else { 64 | throw IllegalStateException("Could not find the referenced type of class $javaClass") 65 | } 66 | } 67 | 68 | return supertype.arguments[0].type!! 69 | } 70 | } 71 | 72 | /** 73 | * Create a [TypeReference] object which references the reified type argument [T]. 74 | */ 75 | public inline fun typeRef(): TypeReference { 76 | return object : TypeReference() { } 77 | } 78 | 79 | /** 80 | * Obtain the full generic type information of the reified type argument [T], usage: `typeOf>()`. 81 | */ 82 | public inline fun typeOf(): Type { 83 | return typeRef().referencedType 84 | } 85 | 86 | /** 87 | * Obtain the full generic type information of the reified type argument [T], usage: `kotlinTypeOf>()`. 88 | */ 89 | public inline fun kotlinTypeOf(): KType { 90 | // Compiler bug: https://youtrack.jetbrains.com/issue/KT-28616 91 | // return typeRef().referencedKotlinType 92 | return kotlin.reflect.typeOf() 93 | } 94 | -------------------------------------------------------------------------------- /ktorm-core/src/main/moditect/module-info.java: -------------------------------------------------------------------------------- 1 | module ktorm.core { 2 | requires transitive java.sql; 3 | requires transitive java.sql.rowset; 4 | requires transitive kotlin.stdlib; 5 | requires transitive kotlin.reflect; 6 | requires static spring.jdbc; 7 | requires static spring.tx; 8 | exports org.ktorm.database; 9 | exports org.ktorm.dsl; 10 | exports org.ktorm.entity; 11 | exports org.ktorm.expression; 12 | exports org.ktorm.logging; 13 | exports org.ktorm.schema; 14 | uses org.ktorm.database.SqlDialect; 15 | } 16 | -------------------------------------------------------------------------------- /ktorm-core/src/test/kotlin/org/ktorm/database/CircularReferenceTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.database 2 | 3 | import org.junit.Test 4 | import org.ktorm.BaseTest 5 | import org.ktorm.dsl.from 6 | import org.ktorm.dsl.joinReferencesAndSelect 7 | import org.ktorm.entity.Entity 8 | import org.ktorm.schema.Table 9 | import org.ktorm.schema.int 10 | 11 | /** 12 | * Created by vince on Dec 19, 2018. 13 | */ 14 | class CircularReferenceTest : BaseTest() { 15 | 16 | interface Foo1 : Entity { 17 | val id: Int 18 | val foo2: Foo2 19 | } 20 | 21 | interface Foo2 : Entity { 22 | val id: Int 23 | val foo3: Foo3 24 | } 25 | 26 | interface Foo3 : Entity { 27 | val id: Int 28 | val foo1: Foo1 29 | } 30 | 31 | object Foos1 : Table("foo1") { 32 | val id = int("id").primaryKey().bindTo { it.id } 33 | val r1 = int("r1").references(Foos2) { it.foo2 } 34 | } 35 | 36 | object Foos2 : Table("foo2") { 37 | val id = int("id").primaryKey().bindTo { it.id } 38 | val r2 = int("r2").references(Foos3) { it.foo3 } 39 | } 40 | 41 | object Foos3 : Table("foo3") { 42 | val id = int("id").primaryKey().bindTo { it.id } 43 | val r3 = int("r3").references(Foos1) { it.foo1 } 44 | } 45 | 46 | @Test 47 | fun testCircularReference() { 48 | try { 49 | database.from(Foos1).joinReferencesAndSelect() 50 | throw AssertionError("unexpected") 51 | 52 | } catch (e: ExceptionInInitializerError) { 53 | val ex = e.cause as IllegalStateException 54 | println(ex.message) 55 | } 56 | } 57 | 58 | interface Bar : Entity { 59 | val id: Int 60 | val bar: Bar 61 | } 62 | 63 | object Bars : Table("bar") { 64 | val id = int("id").primaryKey().bindTo { it.id } 65 | val r = int("r").references(Bars) { it.bar } 66 | } 67 | 68 | @Test 69 | fun test() { 70 | try { 71 | database.from(Bars).joinReferencesAndSelect() 72 | throw AssertionError("unexpected") 73 | 74 | } catch (e: ExceptionInInitializerError) { 75 | val ex = e.cause as IllegalStateException 76 | println(ex.message) 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /ktorm-core/src/test/kotlin/org/ktorm/database/CompoundKeysTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.database 2 | 3 | import org.junit.Test 4 | import org.ktorm.BaseTest 5 | import org.ktorm.dsl.eq 6 | import org.ktorm.dsl.from 7 | import org.ktorm.dsl.joinReferencesAndSelect 8 | import org.ktorm.entity.* 9 | import org.ktorm.schema.* 10 | import java.time.LocalDate 11 | 12 | /** 13 | * Created by vince at Apr 07, 2020. 14 | */ 15 | class CompoundKeysTest : BaseTest() { 16 | 17 | interface Staff : Entity { 18 | var id: Int 19 | var departmentId: Int 20 | var name: String 21 | var job: String 22 | var managerId: Int 23 | var hireDate: LocalDate 24 | var salary: Long 25 | } 26 | 27 | object Staffs : Table("t_employee") { 28 | val id = int("id").primaryKey().bindTo { it.id } 29 | val departmentId = int("department_id").primaryKey().bindTo { it.departmentId } 30 | val name = varchar("name").bindTo { it.name } 31 | val job = varchar("job").bindTo { it.job } 32 | val managerId = int("manager_id").bindTo { it.managerId } 33 | val hireDate = date("hire_date").bindTo { it.hireDate } 34 | val salary = long("salary").bindTo { it.salary } 35 | } 36 | 37 | interface StaffRef : Entity { 38 | val id: Int 39 | val staff: Staff 40 | } 41 | 42 | object StaffRefs : Table("t_staff_ref") { 43 | val id = int("id").primaryKey().bindTo { it.id } 44 | val staffId = int("staff_id").references(Staffs) { it.staff } 45 | } 46 | 47 | val Database.staffs get() = this.sequenceOf(Staffs) 48 | 49 | val Database.staffRefs get() = this.sequenceOf(StaffRefs) 50 | 51 | @Test 52 | fun testAdd() { 53 | val staff = Entity.create() 54 | staff.departmentId = 1 55 | staff.name = "jerry" 56 | staff.job = "engineer" 57 | staff.managerId = 1 58 | staff.hireDate = LocalDate.now() 59 | staff.salary = 100 60 | database.staffs.add(staff) 61 | println(staff) 62 | assert(staff.id == 0) 63 | } 64 | 65 | @Test 66 | fun testFlushChanges() { 67 | var staff = database.staffs.find { it.id eq 2 } ?: throw AssertionError() 68 | staff.job = "engineer" 69 | staff.salary = 100 70 | staff.flushChanges() 71 | staff.flushChanges() 72 | 73 | staff = database.staffs.find { it.id eq 2 } ?: throw AssertionError() 74 | assert(staff.job == "engineer") 75 | assert(staff.salary == 100L) 76 | } 77 | 78 | @Test 79 | fun testDeleteEntity() { 80 | val staff = database.staffs.find { it.id eq 2 } ?: throw AssertionError() 81 | staff.delete() 82 | 83 | assert(database.staffs.count() == 3) 84 | } 85 | 86 | @Test 87 | fun testUpdatePrimaryKey() { 88 | try { 89 | val staff = database.staffs.find { it.id eq 1 } ?: return 90 | staff.departmentId = 2 91 | throw AssertionError() 92 | 93 | } catch (e: UnsupportedOperationException) { 94 | // expected 95 | println(e.message) 96 | } 97 | } 98 | 99 | @Test 100 | fun testReferenceTableWithCompoundKeys() { 101 | try { 102 | database.from(StaffRefs).joinReferencesAndSelect() 103 | throw AssertionError("unexpected") 104 | 105 | } catch (e: ExceptionInInitializerError) { 106 | val ex = e.cause as IllegalStateException 107 | println(ex.message) 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /ktorm-core/src/test/kotlin/org/ktorm/dsl/AggregationTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.dsl 2 | 3 | import org.junit.Test 4 | import org.ktorm.BaseTest 5 | import org.ktorm.entity.* 6 | 7 | /** 8 | * Created by vince on Dec 09, 2018. 9 | */ 10 | class AggregationTest : BaseTest() { 11 | 12 | @Test 13 | fun testCount() { 14 | val count = database.employees.count { it.departmentId eq 1 } 15 | assert(count == 2) 16 | } 17 | 18 | @Test 19 | fun testCountAll() { 20 | val count = database.employees.count() 21 | assert(count == 4) 22 | } 23 | 24 | @Test 25 | fun testSum() { 26 | val sum = database.employees.sumBy { it.salary + 1 } 27 | assert(sum == 454L) 28 | } 29 | 30 | @Test 31 | fun testMax() { 32 | val max = database.employees.maxBy { it.salary - 1 } 33 | assert(max == 199L) 34 | } 35 | 36 | @Test 37 | fun testMin() { 38 | val min = database.employees.minBy { it.salary } 39 | assert(min == 50L) 40 | } 41 | 42 | @Test 43 | fun testAvg() { 44 | val avg = database.employees.averageBy { it.salary } 45 | println(avg) 46 | } 47 | 48 | @Test 49 | fun testNone() { 50 | assert(database.employees.none { it.salary gt 200L }) 51 | } 52 | 53 | @Test 54 | fun testAny() { 55 | assert(!database.employees.any { it.salary gt 200L }) 56 | } 57 | 58 | @Test 59 | fun testAll() { 60 | assert(database.employees.all { it.salary gt 0L }) 61 | } 62 | 63 | @Test 64 | fun testAggregate() { 65 | val result = database.employees.aggregateColumns { max(it.salary) - min(it.salary) } 66 | println(result) 67 | assert(result == 150L) 68 | } 69 | 70 | @Test 71 | fun testAggregate2() { 72 | val (max, min) = database.employees.aggregateColumns { tupleOf(max(it.salary), min(it.salary)) } 73 | assert(max == 200L) 74 | assert(min == 50L) 75 | } 76 | 77 | @Test 78 | fun testGroupAggregate3() { 79 | database.employees 80 | .groupingBy { it.departmentId } 81 | .aggregateColumns { tupleOf(max(it.salary), min(it.salary)) } 82 | .forEach { departmentId, (max, min) -> 83 | println("$departmentId:$max:$min") 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /ktorm-core/src/test/kotlin/org/ktorm/dsl/JoinTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.dsl 2 | 3 | import org.junit.Test 4 | import org.ktorm.BaseTest 5 | 6 | /** 7 | * Created by vince on Dec 08, 2018. 8 | */ 9 | class JoinTest : BaseTest() { 10 | 11 | @Test 12 | fun testCrossJoin() { 13 | val query = database.from(Employees).crossJoin(Departments).select() 14 | assert(query.rowSet.size() == 8) 15 | } 16 | 17 | @Test 18 | fun testJoinWithConditions() { 19 | val names = database 20 | .from(Employees) 21 | .leftJoin(Departments, on = Employees.departmentId eq Departments.id) 22 | .select(Employees.name, Departments.name) 23 | .where { Employees.managerId.isNull() } 24 | .associate { it.getString(1) to it.getString(2) } 25 | 26 | assert(names.size == 2) 27 | assert(names["vince"] == "tech") 28 | assert(names["tom"] == "finance") 29 | } 30 | 31 | @Test 32 | fun testMultiJoin() { 33 | data class Names(val name: String, val managerName: String, val departmentName: String) 34 | 35 | val emp = Employees.aliased("emp") 36 | val mgr = Employees.aliased("mgr") 37 | val dept = Departments.aliased("dept") 38 | 39 | val results = database 40 | .from(emp) 41 | .leftJoin(dept, on = emp.departmentId eq dept.id) 42 | .leftJoin(mgr, on = emp.managerId eq mgr.id) 43 | .select(emp.name, mgr.name, dept.name) 44 | .orderBy(emp.id.asc()) 45 | .map { row -> 46 | Names( 47 | name = row[emp.name].orEmpty(), 48 | managerName = row[mgr.name].orEmpty(), 49 | departmentName = row[dept.name].orEmpty() 50 | ) 51 | } 52 | 53 | assert(results.size == 4) 54 | assert(results[0] == Names(name = "vince", managerName = "", departmentName = "tech")) 55 | assert(results[1] == Names(name = "marry", managerName = "vince", departmentName = "tech")) 56 | assert(results[2] == Names(name = "tom", managerName = "", departmentName = "finance")) 57 | assert(results[3] == Names(name = "penny", managerName = "tom", departmentName = "finance")) 58 | } 59 | 60 | @Test 61 | fun testHasColumn() { 62 | data class Names(val name: String, val managerName: String, val departmentName: String) 63 | 64 | val emp = Employees.aliased("emp") 65 | val mgr = Employees.aliased("mgr") 66 | val dept = Departments.aliased("dept") 67 | 68 | val results = database 69 | .from(emp) 70 | .select(emp.name) 71 | .map { 72 | Names( 73 | name = it[emp.name].orEmpty(), 74 | managerName = it[mgr.name].orEmpty(), 75 | departmentName = it[dept.name].orEmpty() 76 | ) 77 | } 78 | 79 | results.forEach(::println) 80 | assert(results.all { it.managerName == "" }) 81 | assert(results.all { it.departmentName == "" }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ktorm-core/src/test/resources/drop-data.sql: -------------------------------------------------------------------------------- 1 | drop table if exists "t_department"; 2 | drop table if exists "t_employee"; 3 | drop table if exists "t_employee0"; 4 | drop table if exists "company"."t_customer"; 5 | drop schema if exists "company"; 6 | -------------------------------------------------------------------------------- /ktorm-core/src/test/resources/init-data.sql: -------------------------------------------------------------------------------- 1 | create table "t_department"( 2 | "id" int not null primary key auto_increment, 3 | "name" varchar(128) not null, 4 | "location" varchar(128) not null, 5 | "mixedCase" varchar(128) 6 | ); 7 | 8 | create table "t_employee"( 9 | "id" int not null primary key auto_increment, 10 | "name" varchar(128) not null, 11 | "job" varchar(128) not null, 12 | "manager_id" int null, 13 | "hire_date" date not null, 14 | "salary" bigint not null, 15 | "department_id" int not null 16 | ); 17 | 18 | create schema "company"; 19 | create table "company"."t_customer" ( 20 | "id" int not null primary key auto_increment, 21 | "name" varchar(128) not null, 22 | "email" varchar(128) not null, 23 | "phone_number" varchar(128) not null 24 | ); 25 | 26 | insert into "t_department"("name", "location") values ('tech', 'Guangzhou'); 27 | insert into "t_department"("name", "location") values ('finance', 'Beijing'); 28 | 29 | insert into "t_employee"("name", "job", "manager_id", "hire_date", "salary", "department_id") 30 | values ('vince', 'engineer', null, '2018-01-01', 100, 1); 31 | insert into "t_employee"("name", "job", "manager_id", "hire_date", "salary", "department_id") 32 | values ('marry', 'trainee', 1, '2019-01-01', 50, 1); 33 | 34 | insert into "t_employee"("name", "job", "manager_id", "hire_date", "salary", "department_id") 35 | values ('tom', 'director', null, '2018-01-01', 200, 2); 36 | insert into "t_employee"("name", "job", "manager_id", "hire_date", "salary", "department_id") 37 | values ('penny', 'assistant', 3, '2019-01-01', 100, 2); 38 | 39 | 40 | 41 | create table "t_employee0"( 42 | "id" int not null primary key auto_increment, 43 | "name" varchar(128) not null, 44 | "job" varchar(128) not null, 45 | "manager_id" int null, 46 | "hire_date" date not null, 47 | "salary" bigint not null, 48 | "department_id" int not null 49 | ); 50 | 51 | insert into "t_employee0"("name", "job", "manager_id", "hire_date", "salary", "department_id") 52 | values ('vince', 'engineer', null, '2018-01-01', 100, 1); 53 | insert into "t_employee0"("name", "job", "manager_id", "hire_date", "salary", "department_id") 54 | values ('marry', 'trainee', 1, '2019-01-01', 50, 1); 55 | 56 | insert into "t_employee0"("name", "job", "manager_id", "hire_date", "salary", "department_id") 57 | values ('tom', 'director', null, '2018-01-01', 200, 2); 58 | insert into "t_employee0"("name", "job", "manager_id", "hire_date", "salary", "department_id") 59 | values ('penny', 'assistant', 3, '2019-01-01', 100, 2); 60 | -------------------------------------------------------------------------------- /ktorm-core/src/test/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | org.slf4j.simpleLogger.logFile=System.err 2 | org.slf4j.simpleLogger.defaultLogLevel=info 3 | org.slf4j.simpleLogger.log.org.ktorm.database=trace 4 | org.slf4j.simpleLogger.showThreadName=false 5 | org.slf4j.simpleLogger.showLogName=false 6 | org.slf4j.simpleLogger.levelInBrackets=true -------------------------------------------------------------------------------- /ktorm-global/ktorm-global.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.modularity") 5 | id("ktorm.publish") 6 | id("ktorm.source-header-check") 7 | } 8 | 9 | dependencies { 10 | api(project(":ktorm-core")) 11 | compileOnly("org.springframework:spring-jdbc:5.0.10.RELEASE") 12 | compileOnly("org.springframework:spring-tx:5.0.10.RELEASE") 13 | testImplementation(project(":ktorm-core", configuration = "testOutput")) 14 | testImplementation("com.h2database:h2:1.4.198") 15 | } 16 | -------------------------------------------------------------------------------- /ktorm-global/src/main/moditect/module-info.java: -------------------------------------------------------------------------------- 1 | module ktorm.global { 2 | requires ktorm.core; 3 | requires static spring.jdbc; 4 | requires static spring.tx; 5 | exports org.ktorm.global; 6 | } 7 | -------------------------------------------------------------------------------- /ktorm-global/src/test/kotlin/org/ktorm/global/BaseGlobalTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.global 2 | 3 | import org.ktorm.BaseTest 4 | import org.ktorm.database.Database 5 | 6 | /** 7 | * Created by vince at Apr 05, 2020. 8 | */ 9 | @Suppress("DEPRECATION") 10 | open class BaseGlobalTest : BaseTest() { 11 | 12 | override fun init() { 13 | database = Database.connectGlobally("jdbc:h2:mem:ktorm;DB_CLOSE_DELAY=-1", alwaysQuoteIdentifiers = true) 14 | execSqlScript("init-data.sql") 15 | } 16 | } -------------------------------------------------------------------------------- /ktorm-global/src/test/kotlin/org/ktorm/global/GlobalAggregationTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.global 2 | 3 | import org.junit.Test 4 | import org.ktorm.dsl.* 5 | import org.ktorm.entity.aggregateColumns 6 | import org.ktorm.entity.groupingBy 7 | import org.ktorm.entity.tupleOf 8 | 9 | /** 10 | * Created by vince at Apr 05, 2020. 11 | */ 12 | @Suppress("DEPRECATION") 13 | class GlobalAggregationTest : BaseGlobalTest() { 14 | 15 | @Test 16 | fun testCount() { 17 | val count = Employees.count { it.departmentId eq 1 } 18 | assert(count == 2) 19 | } 20 | 21 | @Test 22 | fun testCountAll() { 23 | val count = Employees.count() 24 | assert(count == 4) 25 | } 26 | 27 | @Test 28 | fun testSum() { 29 | val sum = Employees.sumBy { it.salary + 1 } 30 | assert(sum == 454L) 31 | } 32 | 33 | @Test 34 | fun testMax() { 35 | val max = Employees.maxBy { it.salary - 1 } 36 | assert(max == 199L) 37 | } 38 | 39 | @Test 40 | fun testMin() { 41 | val min = Employees.minBy { it.salary } 42 | assert(min == 50L) 43 | } 44 | 45 | @Test 46 | fun testAvg() { 47 | val avg = Employees.averageBy { it.salary } 48 | println(avg) 49 | } 50 | 51 | @Test 52 | fun testNone() { 53 | assert(Employees.none { it.salary gt 200L }) 54 | } 55 | 56 | @Test 57 | fun testAny() { 58 | assert(!Employees.any { it.salary gt 200L }) 59 | } 60 | 61 | @Test 62 | fun testAll() { 63 | assert(Employees.all { it.salary gt 0L }) 64 | } 65 | 66 | @Test 67 | fun testAggregate() { 68 | val result = Employees.asSequence().aggregateColumns { max(it.salary) - min(it.salary) } 69 | println(result) 70 | assert(result == 150L) 71 | } 72 | 73 | @Test 74 | fun testAggregate2() { 75 | val (max, min) = Employees.asSequence().aggregateColumns { tupleOf(max(it.salary), min(it.salary)) } 76 | assert(max == 200L) 77 | assert(min == 50L) 78 | } 79 | 80 | @Test 81 | fun testGroupAggregate3() { 82 | Employees 83 | .asSequence() 84 | .groupingBy { it.departmentId } 85 | .aggregateColumns { tupleOf(max(it.salary), min(it.salary)) } 86 | .forEach { departmentId, (max, min) -> 87 | println("$departmentId:$max:$min") 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /ktorm-global/src/test/kotlin/org/ktorm/global/GlobalDatabaseTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.global 2 | 3 | import org.junit.Test 4 | import org.ktorm.database.Database 5 | import org.ktorm.database.asIterable 6 | import org.ktorm.database.use 7 | import org.ktorm.dsl.eq 8 | import org.ktorm.schema.Table 9 | import org.ktorm.schema.varchar 10 | 11 | /** 12 | * Created by vince at Apr 05, 2020. 13 | */ 14 | @Suppress("DEPRECATION") 15 | class GlobalDatabaseTest : BaseGlobalTest() { 16 | 17 | @Test 18 | fun testMetadata() { 19 | with(Database.global) { 20 | println(url) 21 | println(name) 22 | println(productName) 23 | println(productVersion) 24 | println(keywords.toString()) 25 | println(identifierQuoteString) 26 | println(extraNameCharacters) 27 | } 28 | } 29 | 30 | @Test 31 | fun testKeywordWrapping() { 32 | val configs = object : Table("T_CONFIG") { 33 | val key = varchar("KEY").primaryKey() 34 | val value = varchar("VALUE") 35 | } 36 | 37 | useConnection { conn -> 38 | conn.createStatement().use { statement -> 39 | val sql = """CREATE TABLE T_CONFIG(KEY VARCHAR(128) PRIMARY KEY, VALUE VARCHAR(128))""" 40 | statement.executeUpdate(sql) 41 | } 42 | } 43 | 44 | configs.insert { 45 | set(it.key, "test") 46 | set(it.value, "test value") 47 | } 48 | 49 | assert(configs.count { it.key eq "test" } == 1) 50 | 51 | configs.delete { it.key eq "test" } 52 | } 53 | 54 | @Test 55 | fun testTransaction() { 56 | class DummyException : Exception() 57 | 58 | try { 59 | useTransaction { 60 | Departments.insert { 61 | set(it.name, "administration") 62 | set(it.location, LocationWrapper("Hong Kong")) 63 | } 64 | 65 | assert(Departments.count() == 3) 66 | 67 | throw DummyException() 68 | } 69 | 70 | } catch (e: DummyException) { 71 | assert(Departments.count() == 2) 72 | } 73 | } 74 | 75 | @Test 76 | fun testRawSql() { 77 | val names = useConnection { conn -> 78 | val sql = """ 79 | select "name" from "t_employee" 80 | where "department_id" = ? 81 | order by "id" 82 | """ 83 | 84 | conn.prepareStatement(sql).use { statement -> 85 | statement.setInt(1, 1) 86 | statement.executeQuery().asIterable().map { it.getString(1) } 87 | } 88 | } 89 | 90 | assert(names.size == 2) 91 | assert(names[0] == "vince") 92 | assert(names[1] == "marry") 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /ktorm-global/src/test/kotlin/org/ktorm/global/GlobalJoinTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.global 2 | 3 | import org.junit.Test 4 | import org.ktorm.dsl.* 5 | 6 | /** 7 | * Created by vince at Apr 05, 2020. 8 | */ 9 | @Suppress("DEPRECATION") 10 | class GlobalJoinTest : BaseGlobalTest() { 11 | 12 | @Test 13 | fun testCrossJoin() { 14 | val query = Employees.crossJoin(Departments).select() 15 | assert(query.rowSet.size() == 8) 16 | } 17 | 18 | @Test 19 | fun testJoinWithConditions() { 20 | val names = Employees 21 | .leftJoin(Departments, on = Employees.departmentId eq Departments.id) 22 | .select(Employees.name, Departments.name) 23 | .where { Employees.managerId.isNull() } 24 | .associate { it.getString(1) to it.getString(2) } 25 | 26 | assert(names.size == 2) 27 | assert(names["vince"] == "tech") 28 | assert(names["tom"] == "finance") 29 | } 30 | 31 | @Test 32 | fun testMultiJoin() { 33 | data class Names(val name: String, val managerName: String, val departmentName: String) 34 | 35 | val emp = Employees.aliased("emp") 36 | val mgr = Employees.aliased("mgr") 37 | val dept = Departments.aliased("dept") 38 | 39 | val results = emp 40 | .leftJoin(dept, on = emp.departmentId eq dept.id) 41 | .leftJoin(mgr, on = emp.managerId eq mgr.id) 42 | .select(emp.name, mgr.name, dept.name) 43 | .orderBy(emp.id.asc()) 44 | .map { row -> 45 | Names( 46 | name = row[emp.name].orEmpty(), 47 | managerName = row[mgr.name].orEmpty(), 48 | departmentName = row[dept.name].orEmpty() 49 | ) 50 | } 51 | 52 | assert(results.size == 4) 53 | assert(results[0] == Names(name = "vince", managerName = "", departmentName = "tech")) 54 | assert(results[1] == Names(name = "marry", managerName = "vince", departmentName = "tech")) 55 | assert(results[2] == Names(name = "tom", managerName = "", departmentName = "finance")) 56 | assert(results[3] == Names(name = "penny", managerName = "tom", departmentName = "finance")) 57 | } 58 | 59 | @Test 60 | fun testHasColumn() { 61 | data class Names(val name: String, val managerName: String, val departmentName: String) 62 | 63 | val emp = Employees.aliased("emp") 64 | val mgr = Employees.aliased("mgr") 65 | val dept = Departments.aliased("dept") 66 | 67 | val results = emp 68 | .select(emp.name) 69 | .map { 70 | Names( 71 | name = it[emp.name].orEmpty(), 72 | managerName = it[mgr.name].orEmpty(), 73 | departmentName = it[dept.name].orEmpty() 74 | ) 75 | } 76 | 77 | results.forEach(::println) 78 | assert(results.all { it.managerName == "" }) 79 | assert(results.all { it.departmentName == "" }) 80 | } 81 | } -------------------------------------------------------------------------------- /ktorm-jackson/ktorm-jackson.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.modularity") 5 | id("ktorm.publish") 6 | id("ktorm.source-header-check") 7 | } 8 | 9 | dependencies { 10 | api(project(":ktorm-core")) 11 | api("com.fasterxml.jackson.core:jackson-databind:2.12.3") 12 | api("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.3") 13 | api("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.3") 14 | } 15 | 16 | val generatedSourceDir = "${project.layout.buildDirectory.asFile.get()}/generated/source/main/kotlin" 17 | 18 | val generatePackageVersion by tasks.registering(Copy::class) { 19 | from("src/main/kotlin/org/ktorm/jackson/PackageVersion.kt.tmpl") 20 | into("${generatedSourceDir}/org/ktorm/jackson") 21 | rename("(.+)\\.tmpl", "$1") 22 | expand(project.properties) 23 | } 24 | 25 | tasks { 26 | codegen { 27 | dependsOn(generatePackageVersion) 28 | } 29 | } 30 | 31 | sourceSets.main { 32 | kotlin.srcDir(generatedSourceDir) 33 | } 34 | -------------------------------------------------------------------------------- /ktorm-jackson/src/main/kotlin/org/ktorm/jackson/PackageVersion.kt.tmpl: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.jackson 18 | 19 | import com.fasterxml.jackson.core.Version 20 | import com.fasterxml.jackson.core.Versioned 21 | import com.fasterxml.jackson.core.util.VersionUtil 22 | 23 | /** 24 | * Automatically generated from PackageVersion.kt.tmpl. 25 | */ 26 | internal object PackageVersion : Versioned { 27 | 28 | val VERSION: Version = VersionUtil.parseVersion("${version}", "${group}", "${name}") 29 | 30 | override fun version() = VERSION 31 | } 32 | -------------------------------------------------------------------------------- /ktorm-jackson/src/main/moditect/module-info.java: -------------------------------------------------------------------------------- 1 | module ktorm.jackson { 2 | requires ktorm.core; 3 | requires com.fasterxml.jackson.databind; 4 | requires com.fasterxml.jackson.kotlin; 5 | requires com.fasterxml.jackson.datatype.jsr310; 6 | exports org.ktorm.jackson; 7 | provides com.fasterxml.jackson.databind.Module with org.ktorm.jackson.KtormModule; 8 | } 9 | -------------------------------------------------------------------------------- /ktorm-jackson/src/main/resources/META-INF/services/com.fasterxml.jackson.databind.Module: -------------------------------------------------------------------------------- 1 | org.ktorm.jackson.KtormModule -------------------------------------------------------------------------------- /ktorm-ksp-annotations/ktorm-ksp-annotations.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.modularity") 5 | id("ktorm.publish") 6 | id("ktorm.source-header-check") 7 | } 8 | 9 | dependencies { 10 | api(project(":ktorm-core")) 11 | } 12 | -------------------------------------------------------------------------------- /ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/Column.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.annotation 18 | 19 | import org.ktorm.schema.SqlType 20 | import kotlin.reflect.KClass 21 | 22 | /** 23 | * Specify the mapped column for an entity property. If no `@Column` annotation is specified, the default values apply. 24 | */ 25 | @Target(AnnotationTarget.PROPERTY) 26 | @Retention(AnnotationRetention.SOURCE) 27 | public annotation class Column( 28 | 29 | /** 30 | * The name of the column. 31 | * 32 | * If not specified, the name will be generated by a naming strategy. This naming strategy can be configured 33 | * by KSP option `ktorm.dbNamingStrategy`, which accepts the following values: 34 | * 35 | * - lower-snake-case (default): generate lower snake-case names, for example: userId --> user_id. 36 | * 37 | * - upper-snake-case: generate upper snake-case names, for example: userId --> USER_ID. 38 | * 39 | * - Class name of a custom naming strategy, which should be an implementation of 40 | * `org.ktorm.ksp.spi.DatabaseNamingStrategy`. 41 | */ 42 | val name: String = "", 43 | 44 | /** 45 | * The SQL type of the column. 46 | * 47 | * If not specified, the SQL type will be automatically inferred by the annotated property's Kotlin type. 48 | * The specified class must be a Kotlin singleton object or a normal class with a constructor that accepts 49 | * a single [org.ktorm.schema.TypeReference] argument. 50 | */ 51 | val sqlType: KClass> = Nothing::class, 52 | 53 | /** 54 | * The name of the corresponding column property in the generated table class. 55 | * 56 | * If not specified, the name will be the annotated property's name. This behavior can be configured by KSP option 57 | * `ktorm.codingNamingStrategy`, which accepts an implementation class name of 58 | * `org.ktorm.ksp.spi.CodingNamingStrategy`. 59 | */ 60 | val propertyName: String = "", 61 | ) 62 | -------------------------------------------------------------------------------- /ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/Ignore.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.annotation 18 | 19 | /** 20 | * Ignore the annotated property, not generating the column definition. 21 | */ 22 | @Target(AnnotationTarget.PROPERTY) 23 | @Retention(AnnotationRetention.SOURCE) 24 | public annotation class Ignore 25 | -------------------------------------------------------------------------------- /ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/PrimaryKey.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.annotation 18 | 19 | /** 20 | * Mark the annotated column as a primary key. 21 | */ 22 | @Target(AnnotationTarget.PROPERTY) 23 | @Retention(AnnotationRetention.SOURCE) 24 | public annotation class PrimaryKey 25 | -------------------------------------------------------------------------------- /ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/References.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.annotation 18 | 19 | /** 20 | * Specify the mapped column for an entity property, and bind this column to a reference table. Typically, 21 | * this column is a foreign key in relational databases. Entity sequence APIs would automatically left-join 22 | * all references (recursively) by default. 23 | */ 24 | @Target(AnnotationTarget.PROPERTY) 25 | @Retention(AnnotationRetention.SOURCE) 26 | public annotation class References( 27 | 28 | /** 29 | * The name of the column. 30 | * 31 | * If not specified, the name will be generated by a naming strategy. This naming strategy can be configured 32 | * by KSP option `ktorm.dbNamingStrategy`, which accepts the following values: 33 | * 34 | * - lower-snake-case (default): generate names in lower snake-case, the names are concatenation of the current 35 | * property's name and the referenced table's primary key name. For example, a property `user` referencing a table 36 | * that has a primary key named `id` will get a name `user_id`. 37 | * 38 | * - upper-snake-case: generate names in upper snake-case, the names are concatenation of the current 39 | * property's name and the referenced table's primary key name. For example, a property `user` referencing a table 40 | * that has a primary key named `id` will get a name `USER_ID`. 41 | * 42 | * - Class name of a custom naming strategy, which should be an implementation of 43 | * `org.ktorm.ksp.spi.DatabaseNamingStrategy`. 44 | */ 45 | val name: String = "", 46 | 47 | /** 48 | * The name of the corresponding column property in the generated table class. 49 | * 50 | * If not specified, the name will be the concatenation of the current property's name and the referenced table's 51 | * primary key name. This behavior can be configured by KSP option `ktorm.codingNamingStrategy`, which accepts 52 | * an implementation class name of `org.ktorm.ksp.spi.CodingNamingStrategy`. 53 | */ 54 | val propertyName: String = "", 55 | 56 | /** 57 | * The name of the corresponding referenced table property in the Refs wrapper class. 58 | * 59 | * If not specified, the name will be the annotated property's name. This behavior can be configured by KSP option 60 | * `ktorm.codingNamingStrategy`, which accepts an implementation class name of 61 | * `org.ktorm.ksp.spi.CodingNamingStrategy`. 62 | */ 63 | val refTablePropertyName: String = "", 64 | ) 65 | -------------------------------------------------------------------------------- /ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/Table.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.annotation 18 | 19 | /** 20 | * Specify the table for an entity class. 21 | */ 22 | @Target(AnnotationTarget.CLASS) 23 | @Retention(AnnotationRetention.SOURCE) 24 | public annotation class Table( 25 | 26 | /** 27 | * The name of the table. 28 | * 29 | * If not specified, the name will be generated by a naming strategy. This naming strategy can be configured 30 | * by KSP option `ktorm.dbNamingStrategy`, which accepts the following values: 31 | * 32 | * - lower-snake-case (default): generate lower snake-case names, for example: UserProfile --> user_profile. 33 | * 34 | * - upper-snake-case: generate upper snake-case names, for example: UserProfile --> USER_PROFILE. 35 | * 36 | * - Class name of a custom naming strategy, which should be an implementation of 37 | * `org.ktorm.ksp.spi.DatabaseNamingStrategy`. 38 | */ 39 | val name: String = "", 40 | 41 | /** 42 | * The alias of the table. 43 | */ 44 | val alias: String = "", 45 | 46 | /** 47 | * The catalog of the table. 48 | * 49 | * The default value can be configured by KSP option `ktorm.catalog`. 50 | */ 51 | val catalog: String = "", 52 | 53 | /** 54 | * The schema of the table. 55 | * 56 | * The default value can be configured by KSP option `ktorm.schema`. 57 | */ 58 | val schema: String = "", 59 | 60 | /** 61 | * The name of the corresponding table class in the generated code. 62 | * 63 | * If not specified, the name will be the plural form of the annotated class's name, 64 | * for example: UserProfile --> UserProfiles. This behavior can be configured by KSP option 65 | * `ktorm.codingNamingStrategy`, which accepts an implementation class name of 66 | * `org.ktorm.ksp.spi.CodingNamingStrategy`. 67 | */ 68 | val className: String = "", 69 | 70 | /** 71 | * The name of the corresponding entity sequence in the generated code. 72 | * 73 | * If not specified, the name will be the plural form of the annotated class's name, with the first word in 74 | * lower case, for example: UserProfile --> userProfiles. This behavior can be configured by KSP option 75 | * `ktorm.codingNamingStrategy`, which accepts an implementation class name of 76 | * `org.ktorm.ksp.spi.CodingNamingStrategy`. 77 | */ 78 | val entitySequenceName: String = "", 79 | 80 | /** 81 | * Specify properties that should be ignored for generating column definitions. 82 | */ 83 | val ignoreProperties: Array = [], 84 | ) 85 | -------------------------------------------------------------------------------- /ktorm-ksp-annotations/src/main/moditect/module-info.java: -------------------------------------------------------------------------------- 1 | module ktorm.ksp.annotations { 2 | requires ktorm.core; 3 | requires jdk.unsupported; 4 | exports org.ktorm.ksp.annotation; 5 | } 6 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler-maven-plugin/ktorm-ksp-compiler-maven-plugin.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.publish") 5 | id("ktorm.source-header-check") 6 | } 7 | 8 | dependencies { 9 | compileOnly(kotlin("maven-plugin")) 10 | compileOnly(kotlin("compiler")) 11 | compileOnly("org.apache.maven:maven-core:3.9.3") 12 | implementation("com.google.devtools.ksp:symbol-processing-cmdline:1.9.23-1.0.20") 13 | implementation(project(":ktorm-ksp-compiler")) { 14 | exclude(group = "com.pinterest.ktlint", module = "ktlint-rule-engine") 15 | exclude(group = "com.pinterest.ktlint", module = "ktlint-ruleset-standard") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler-maven-plugin/src/main/resources/META-INF/plexus/components.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | org.jetbrains.kotlin.maven.KotlinMavenPluginExtension 6 | ksp 7 | org.ktorm.ksp.compiler.maven.KtormKspMavenPluginExtension 8 | false 9 | 10 | 11 | org.apache.maven.repository.RepositorySystem 12 | repositorySystem 13 | 14 | 15 | org.apache.maven.execution.MavenSession 16 | mavenSession 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/ktorm-ksp-compiler.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.publish") 5 | id("ktorm.source-header-check") 6 | } 7 | 8 | dependencies { 9 | implementation(project(":ktorm-core")) 10 | implementation(project(":ktorm-ksp-annotations")) 11 | implementation(project(":ktorm-ksp-spi")) 12 | implementation("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20") 13 | implementation("com.squareup:kotlinpoet-ksp:1.11.0") 14 | implementation("org.atteo:evo-inflector:1.3") 15 | implementation("com.pinterest.ktlint:ktlint-rule-engine:0.50.0") { 16 | exclude(group = "org.jetbrains.kotlin", module = "kotlin-compiler-embeddable") 17 | } 18 | implementation("com.pinterest.ktlint:ktlint-ruleset-standard:0.50.0") { 19 | exclude(group = "org.jetbrains.kotlin", module = "kotlin-compiler-embeddable") 20 | } 21 | 22 | testImplementation("com.github.tschuchortdev:kotlin-compile-testing:1.5.0") 23 | testImplementation("com.github.tschuchortdev:kotlin-compile-testing-ksp:1.5.0") 24 | testImplementation("com.h2database:h2:1.4.198") 25 | testImplementation("org.slf4j:slf4j-simple:2.0.3") 26 | } 27 | 28 | if (JavaVersion.current() >= JavaVersion.VERSION_1_9) { 29 | tasks.test { 30 | jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/formatter/CodeFormatter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.compiler.formatter 18 | 19 | /** 20 | * Code formatter interface. 21 | */ 22 | internal fun interface CodeFormatter { 23 | 24 | /** 25 | * Format the generated code to the community recommended coding style. 26 | */ 27 | fun format(fileName: String, code: String): String 28 | } 29 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/formatter/KtLintCodeFormatter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.compiler.formatter 18 | 19 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 20 | import com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 21 | import com.pinterest.ktlint.rule.engine.api.Code 22 | import com.pinterest.ktlint.rule.engine.api.EditorConfigDefaults 23 | import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine 24 | import org.ec4j.core.EditorConfigLoader 25 | import org.ec4j.core.Resource.Resources 26 | import java.util.* 27 | 28 | internal class KtLintCodeFormatter(val environment: SymbolProcessorEnvironment) : CodeFormatter { 29 | private val ktLintRuleEngine = KtLintRuleEngine( 30 | ruleProviders = ServiceLoader 31 | .load(RuleSetProviderV3::class.java, javaClass.classLoader) 32 | .flatMap { it.getRuleProviders() } 33 | .toSet(), 34 | editorConfigDefaults = EditorConfigDefaults( 35 | EditorConfigLoader.default_().load( 36 | Resources.ofClassPath(javaClass.classLoader, "/ktorm-ksp-compiler/.editorconfig", Charsets.UTF_8) 37 | ) 38 | ) 39 | ) 40 | 41 | override fun format(fileName: String, code: String): String { 42 | try { 43 | // Manually fix some code styles before formatting. 44 | val snippet = code 45 | .replace(Regex("""\(\s*"""), "(") 46 | .replace(Regex("""\s*\)"""), ")") 47 | .replace(Regex(""",\s*"""), ", ") 48 | .replace(Regex(""",\s*\)"""), ")") 49 | .replace(Regex("""\s+get\(\)\s="""), " get() =") 50 | .replace(Regex("""\s+=\s+"""), " = ") 51 | .replace("import org.ktorm.ksp.`annotation`", "import org.ktorm.ksp.annotation") 52 | 53 | return ktLintRuleEngine.format(Code.fromSnippet(snippet)) 54 | } catch (e: Throwable) { 55 | environment.logger.exception(e) 56 | return code 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/ComponentFunctionGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.compiler.generator 18 | 19 | import com.google.devtools.ksp.isAbstract 20 | import com.squareup.kotlinpoet.FunSpec 21 | import com.squareup.kotlinpoet.KModifier 22 | import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview 23 | import com.squareup.kotlinpoet.ksp.toClassName 24 | import com.squareup.kotlinpoet.ksp.toTypeName 25 | import org.ktorm.entity.Entity 26 | import org.ktorm.ksp.compiler.util._type 27 | import org.ktorm.ksp.spi.TableMetadata 28 | import kotlin.reflect.full.memberProperties 29 | 30 | @OptIn(KotlinPoetKspPreview::class) 31 | internal object ComponentFunctionGenerator { 32 | 33 | fun generate(table: TableMetadata): Sequence { 34 | val skipNames = Entity::class.memberProperties.map { it.name }.toSet() 35 | return table.entityClass.getAllProperties() 36 | .filter { it.isAbstract() } 37 | .filter { it.simpleName.asString() !in skipNames } 38 | .mapIndexed { i, prop -> 39 | FunSpec.builder("component${i + 1}") 40 | .addKdoc("Return the value of [%L.%L]. ", 41 | table.entityClass.simpleName.asString(), prop.simpleName.asString()) 42 | .addModifiers(KModifier.OPERATOR) 43 | .receiver(table.entityClass.toClassName()) 44 | .returns(prop._type.toTypeName()) 45 | .addCode("return·this.%N", prop.simpleName.asString()) 46 | .build() 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/CopyFunctionGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.compiler.generator 18 | 19 | import com.squareup.kotlinpoet.FunSpec 20 | import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview 21 | import com.squareup.kotlinpoet.ksp.toClassName 22 | import org.ktorm.ksp.spi.TableMetadata 23 | 24 | @OptIn(KotlinPoetKspPreview::class) 25 | internal object CopyFunctionGenerator { 26 | 27 | fun generate(table: TableMetadata): FunSpec { 28 | val kdoc = "" + 29 | "Return a deep copy of this entity (which has the same property values and tracked statuses), " + 30 | "and alter the specified property values. " 31 | 32 | return FunSpec.builder("copy") 33 | .addKdoc(kdoc) 34 | .receiver(table.entityClass.toClassName()) 35 | .addParameters(PseudoConstructorFunctionGenerator.buildParameters(table).asIterable()) 36 | .returns(table.entityClass.toClassName()) 37 | .addCode(PseudoConstructorFunctionGenerator.buildFunctionBody(table, isCopy = true)) 38 | .build() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/EntitySequencePropertyGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.compiler.generator 18 | 19 | import com.squareup.kotlinpoet.* 20 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 21 | import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview 22 | import com.squareup.kotlinpoet.ksp.toClassName 23 | import org.ktorm.database.Database 24 | import org.ktorm.entity.EntitySequence 25 | import org.ktorm.ksp.spi.TableMetadata 26 | 27 | @OptIn(KotlinPoetKspPreview::class) 28 | internal object EntitySequencePropertyGenerator { 29 | 30 | fun generate(table: TableMetadata): PropertySpec { 31 | val entityClass = table.entityClass.toClassName() 32 | val tableClass = ClassName(table.entityClass.packageName.asString(), table.tableClassName) 33 | val entitySequenceType = EntitySequence::class.asClassName().parameterizedBy(entityClass, tableClass) 34 | 35 | return PropertySpec.builder(table.entitySequenceName, entitySequenceType) 36 | .addKdoc("Return the default entity sequence of [%L].", table.tableClassName) 37 | .receiver(Database::class.asClassName()) 38 | .getter( 39 | FunSpec.getterBuilder() 40 | .addStatement( 41 | "return·this.%M(%N)", 42 | MemberName("org.ktorm.entity", "sequenceOf", true), 43 | table.tableClassName) 44 | .build()) 45 | .build() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/RefsClassGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.compiler.generator 18 | 19 | import com.squareup.kotlinpoet.* 20 | import org.ktorm.ksp.spi.TableMetadata 21 | import org.ktorm.schema.Table 22 | 23 | /** 24 | * Created by vince at Jul 15, 2023. 25 | */ 26 | internal object RefsClassGenerator { 27 | 28 | fun generate(table: TableMetadata): TypeSpec { 29 | val tableClass = ClassName(table.entityClass.packageName.asString(), table.tableClassName) 30 | 31 | val typeSpec = TypeSpec.classBuilder("${table.tableClassName}Refs") 32 | .addKdoc("Wrapper class that provides a convenient way to access referenced tables.") 33 | .primaryConstructor(FunSpec.constructorBuilder().addParameter("t", tableClass).build()) 34 | 35 | for (column in table.columns) { 36 | val refTable = column.referenceTable ?: continue 37 | val refTableClass = ClassName(refTable.entityClass.packageName.asString(), refTable.tableClassName) 38 | 39 | val propertySpec = PropertySpec.builder(column.refTablePropertyName!!, refTableClass) 40 | .addKdoc("Return the referenced table of [${table.tableClassName}.${column.columnPropertyName}].") 41 | .initializer(CodeBlock.of("t.%N.referenceTable as %T", column.columnPropertyName, refTableClass)) 42 | .build() 43 | 44 | typeSpec.addProperty(propertySpec) 45 | } 46 | 47 | val funSpec = FunSpec.builder("toList") 48 | .addKdoc("Return all referenced tables as a list.") 49 | .returns(typeNameOf>>()) 50 | .addCode("return·listOf(") 51 | 52 | for (column in table.columns) { 53 | val refTablePropertyName = column.refTablePropertyName ?: continue 54 | funSpec.addCode("%N, ", refTablePropertyName) 55 | } 56 | 57 | typeSpec.addFunction(funSpec.addCode(")").build()) 58 | return typeSpec.build() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/RefsPropertyGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.compiler.generator 18 | 19 | import com.squareup.kotlinpoet.ClassName 20 | import com.squareup.kotlinpoet.FunSpec 21 | import com.squareup.kotlinpoet.PropertySpec 22 | import org.ktorm.ksp.spi.TableMetadata 23 | 24 | /** 25 | * Created by vince at Jul 15, 2023. 26 | */ 27 | internal object RefsPropertyGenerator { 28 | 29 | fun generate(table: TableMetadata): PropertySpec { 30 | val tableClass = ClassName(table.entityClass.packageName.asString(), table.tableClassName) 31 | val refsClass = ClassName(table.entityClass.packageName.asString(), "${table.tableClassName}Refs") 32 | 33 | return PropertySpec.builder("refs", refsClass) 34 | .addKdoc("Return the refs object that provides a convenient way to access referenced tables.") 35 | .receiver(tableClass) 36 | .getter(FunSpec.getterBuilder().addStatement("return·%T(this)", refsClass).build()) 37 | .build() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider: -------------------------------------------------------------------------------- 1 | org.ktorm.ksp.compiler.KtormProcessorProvider 2 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/main/resources/ktorm-ksp-compiler/.editorconfig: -------------------------------------------------------------------------------- 1 | # ktlint config used to format the generated code. 2 | [*.{kt,kts}] 3 | ktlint_code_style = ktlint_official 4 | indent_size = 4 5 | indent_style = space 6 | max_line_length = 120 7 | insert_final_newline = true 8 | ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 4 9 | ktlint_function_signature_body_expression_wrapping = multiline 10 | ij_kotlin_allow_trailing_comma = false 11 | ij_kotlin_allow_trailing_comma_on_call_site = false 12 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/AddFunctionGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.ksp.compiler.generator 2 | 3 | import org.junit.Test 4 | import org.ktorm.ksp.compiler.BaseKspTest 5 | 6 | class AddFunctionGeneratorTest : BaseKspTest() { 7 | 8 | @Test 9 | fun testGenerateKey() = runKotlin(""" 10 | @Table 11 | data class User( 12 | @PrimaryKey 13 | var id: Int, 14 | var username: String, 15 | var age: Int, 16 | ) 17 | 18 | fun run() { 19 | val user = User(0, "test", 100) 20 | database.users.add(user, useGeneratedKey = true) 21 | assert(user.id == 4) 22 | 23 | val users = database.users.toList() 24 | assert(users.size == 4) 25 | assert(users[0] == User(id = 1, username = "jack", age = 20)) 26 | assert(users[1] == User(id = 2, username = "lucy", age = 22)) 27 | assert(users[2] == User(id = 3, username = "mike", age = 22)) 28 | assert(users[3] == User(id = 4, username = "test", age = 100)) 29 | } 30 | """.trimIndent()) 31 | 32 | @Test 33 | fun testNoGenerateKey() = runKotlin(""" 34 | @Table 35 | data class User( 36 | @PrimaryKey 37 | var id: Int, 38 | var username: String, 39 | var age: Int, 40 | ) 41 | 42 | fun run() { 43 | database.users.add(User(99, "test", 100)) 44 | 45 | val users = database.users.toList() 46 | assert(users.size == 4) 47 | assert(users[0] == User(id = 1, username = "jack", age = 20)) 48 | assert(users[1] == User(id = 2, username = "lucy", age = 22)) 49 | assert(users[2] == User(id = 3, username = "mike", age = 22)) 50 | assert(users[3] == User(id = 99, username = "test", age = 100)) 51 | } 52 | """.trimIndent()) 53 | 54 | @Test 55 | fun testNoGenerateKey1() = runKotlin(""" 56 | @Table 57 | data class User( 58 | @PrimaryKey 59 | val id: Int, 60 | val username: String, 61 | val age: Int, 62 | ) 63 | 64 | fun run() { 65 | database.users.add(User(99, "test", 100)) 66 | 67 | val users = database.users.toList() 68 | assert(users.size == 4) 69 | assert(users[0] == User(id = 1, username = "jack", age = 20)) 70 | assert(users[1] == User(id = 2, username = "lucy", age = 22)) 71 | assert(users[2] == User(id = 3, username = "mike", age = 22)) 72 | assert(users[3] == User(id = 99, username = "test", age = 100)) 73 | } 74 | """.trimIndent()) 75 | 76 | @Test 77 | fun testSequenceModified() = runKotlin(""" 78 | @Table 79 | data class User( 80 | @PrimaryKey 81 | val id: Int, 82 | val username: String, 83 | val age: Int, 84 | ) 85 | 86 | fun run() { 87 | try { 88 | val users = database.users.filter { it.id eq 1 } 89 | users.add(User(99, "lucy", 10)) 90 | throw AssertionError("fail") 91 | } catch (_: UnsupportedOperationException) { 92 | } 93 | } 94 | """.trimIndent()) 95 | } 96 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/ComponentFunctionGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.ksp.compiler.generator 2 | 3 | import org.junit.Test 4 | import org.ktorm.ksp.compiler.BaseKspTest 5 | 6 | class ComponentFunctionGeneratorTest : BaseKspTest() { 7 | 8 | @Test 9 | fun `interface entity component function`() = runKotlin(""" 10 | @Table 11 | interface Employee: Entity { 12 | @PrimaryKey 13 | var id: Int? 14 | var name: String 15 | var job: String 16 | @Ignore 17 | var hireDate: LocalDate 18 | } 19 | 20 | fun run() { 21 | val today = LocalDate.now() 22 | 23 | val employee = Entity.create() 24 | employee.id = 1 25 | employee.name = "name" 26 | employee.job = "job" 27 | employee.hireDate = today 28 | 29 | val (id, name, job, hireDate) = employee 30 | assert(id == 1) 31 | assert(name == "name") 32 | assert(job == "job") 33 | assert(hireDate == today) 34 | } 35 | """.trimIndent()) 36 | } 37 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/CopyFunctionGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.ksp.compiler.generator 2 | 3 | import org.junit.Test 4 | import org.ktorm.ksp.compiler.BaseKspTest 5 | 6 | class CopyFunctionGeneratorTest : BaseKspTest() { 7 | 8 | @Test 9 | fun `interface entity copy function`() = runKotlin(""" 10 | @Table 11 | interface Employee: Entity { 12 | @PrimaryKey 13 | var id: Int? 14 | var name: String 15 | var job: String 16 | @Ignore 17 | var hireDate: LocalDate 18 | } 19 | 20 | fun run() { 21 | val today = LocalDate.now() 22 | 23 | val jack = Employee(name = "jack", job = "programmer", hireDate = today) 24 | val tom = jack.copy(name = "tom") 25 | 26 | assert(tom != jack) 27 | assert(tom !== jack) 28 | assert(tom.name == "tom") 29 | assert(tom.job == "programmer") 30 | assert(tom.hireDate == today) 31 | 32 | with(EntityExtensionsApi()) { 33 | assert(!tom.hasColumnValue(Employees.id.binding!!)) 34 | } 35 | } 36 | """.trimIndent()) 37 | } 38 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/EntitySequencePropertyGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.ksp.compiler.generator 2 | 3 | import org.junit.Test 4 | import org.ktorm.ksp.compiler.BaseKspTest 5 | 6 | class EntitySequencePropertyGeneratorTest : BaseKspTest() { 7 | 8 | @Test 9 | fun `sequenceOf function`() = runKotlin(""" 10 | @Table 11 | data class User( 12 | @PrimaryKey 13 | var id: Int, 14 | var username: String, 15 | var age: Int 16 | ) 17 | 18 | @Table 19 | interface Employee: Entity { 20 | @PrimaryKey 21 | var id: Int 22 | var name: String 23 | var job: String 24 | var hireDate: LocalDate 25 | } 26 | 27 | fun run() { 28 | val users = database.users.toList() 29 | assert(users.size == 3) 30 | assert(users[0] == User(id = 1, username = "jack", age = 20)) 31 | assert(users[1] == User(id = 2, username = "lucy", age = 22)) 32 | assert(users[2] == User(id = 3, username = "mike", age = 22)) 33 | 34 | val employees = database.employees.toList() 35 | assert(employees.size == 4) 36 | assert(employees[0] == Employee(id = 1, name = "vince", job = "engineer", hireDate = LocalDate.of(2018, 1, 1))) 37 | assert(employees[1] == Employee(id = 2, name = "marry", job = "trainee", hireDate = LocalDate.of(2019, 1, 1))) 38 | assert(employees[2] == Employee(id = 3, name = "tom", job = "director", hireDate = LocalDate.of(2018, 1, 1))) 39 | assert(employees[3] == Employee(id = 4, name = "penny", job = "assistant", hireDate = LocalDate.of(2019, 1, 1))) 40 | } 41 | """.trimIndent()) 42 | 43 | @Test 44 | fun `custom sequence name`() = runKotlin(""" 45 | @Table(entitySequenceName = "aUsers") 46 | data class User( 47 | @PrimaryKey 48 | var id: Int, 49 | var username: String, 50 | var age: Int 51 | ) 52 | 53 | fun run() { 54 | val users = database.aUsers.toList() 55 | assert(users.size == 3) 56 | assert(users[0] == User(id = 1, username = "jack", age = 20)) 57 | assert(users[1] == User(id = 2, username = "lucy", age = 22)) 58 | assert(users[2] == User(id = 3, username = "mike", age = 22)) 59 | } 60 | """.trimIndent()) 61 | } 62 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/ExtCodeGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.ksp.compiler.generator 2 | 3 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 4 | import com.squareup.kotlinpoet.FunSpec 5 | import com.squareup.kotlinpoet.PropertySpec 6 | import com.squareup.kotlinpoet.TypeSpec 7 | import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview 8 | import com.squareup.kotlinpoet.ksp.toClassName 9 | import com.squareup.kotlinpoet.typeNameOf 10 | import org.ktorm.ksp.spi.ExtCodeGenerator 11 | import org.ktorm.ksp.spi.TableMetadata 12 | 13 | /** 14 | * Created by vince at May 27, 2023. 15 | */ 16 | @OptIn(KotlinPoetKspPreview::class) 17 | class ExtCodeGeneratorTest : ExtCodeGenerator { 18 | 19 | override fun generateTypes(table: TableMetadata, environment: SymbolProcessorEnvironment): List { 20 | return listOf(TypeSpec.classBuilder("TestFor" + table.tableClassName).build()) 21 | } 22 | 23 | override fun generateProperties(table: TableMetadata, environment: SymbolProcessorEnvironment): List { 24 | return listOf( 25 | PropertySpec.builder("pTest", typeNameOf()) 26 | .addKdoc("This is a test property.") 27 | .receiver(table.entityClass.toClassName()) 28 | .getter(FunSpec.getterBuilder().addStatement("return 0").build()) 29 | .build() 30 | ) 31 | } 32 | 33 | override fun generateFunctions(table: TableMetadata, environment: SymbolProcessorEnvironment): List { 34 | return listOf( 35 | FunSpec.builder("fTest") 36 | .addKdoc("This is a test function. \n\n @since 3.6.0") 37 | .receiver(table.entityClass.toClassName()) 38 | .returns(typeNameOf()) 39 | .addStatement("return 0") 40 | .build() 41 | ) 42 | } 43 | } -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/PseudoConstructorFunctionGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.ksp.compiler.generator 2 | 3 | import org.junit.Test 4 | import org.ktorm.ksp.compiler.BaseKspTest 5 | 6 | class PseudoConstructorFunctionGeneratorTest : BaseKspTest() { 7 | 8 | @Test 9 | fun `interface entity constructor function`() = runKotlin(""" 10 | @Table 11 | interface Employee: Entity { 12 | @PrimaryKey 13 | var id: Int? 14 | var name: String 15 | var job: String 16 | @Ignore 17 | var hireDate: LocalDate 18 | } 19 | 20 | fun run() { 21 | assert(Employee().toString() == "Employee()") 22 | assert(Employee(id = null).toString() == "Employee(id=null)") 23 | assert(Employee(id = null, name = "").toString() == "Employee(id=null, name=)") 24 | assert(Employee(1, "vince", "engineer", LocalDate.of(2023, 1, 1)).toString() == "Employee(id=1, name=vince, job=engineer, hireDate=2023-01-01)") 25 | } 26 | """.trimIndent()) 27 | } 28 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/RefsClassGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.ksp.compiler.generator 2 | 3 | import org.junit.Test 4 | import org.ktorm.ksp.compiler.BaseKspTest 5 | 6 | /** 7 | * Created by vince at Jul 16, 2023. 8 | */ 9 | class RefsClassGeneratorTest : BaseKspTest() { 10 | 11 | @Test 12 | fun testRefs() = runKotlin( 13 | """ 14 | @Table 15 | interface Employee: Entity { 16 | @PrimaryKey 17 | var id: Int 18 | var name: String 19 | var job: String 20 | var managerId: Int? 21 | var hireDate: LocalDate 22 | var salary: Long 23 | @References 24 | var department: Department 25 | } 26 | 27 | @Table 28 | interface Department: Entity { 29 | @PrimaryKey 30 | var id: Int 31 | var name: String 32 | var location: String 33 | } 34 | 35 | fun run() { 36 | val employees = database.employees 37 | .filter { it.refs.department.name eq "tech" } 38 | .filter { it.refs.department.location eq "Guangzhou" } 39 | .sortedBy { it.id } 40 | .toList() 41 | 42 | assert(employees.size == 2) 43 | assert(employees[0].name == "vince") 44 | assert(employees[1].name == "marry") 45 | } 46 | """.trimIndent()) 47 | } -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/UpdateFunctionGeneratorTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.ksp.compiler.generator 2 | 3 | import org.junit.Test 4 | import org.ktorm.ksp.compiler.BaseKspTest 5 | 6 | class UpdateFunctionGeneratorTest : BaseKspTest() { 7 | 8 | @Test 9 | fun `sequence update function`() = runKotlin(""" 10 | @Table 11 | data class User( 12 | @PrimaryKey 13 | var id: Int, 14 | var username: String, 15 | var age: Int, 16 | ) 17 | 18 | fun run() { 19 | val user = database.users.first { it.id eq 1 } 20 | assert(user.username == "jack") 21 | 22 | user.username = "tom" 23 | database.users.update(user) 24 | 25 | val user0 = database.users.first { it.id eq 1 } 26 | assert(user0.username == "tom") 27 | } 28 | """.trimIndent()) 29 | 30 | @Test 31 | fun `modified entity sequence call update fun`() = runKotlin(""" 32 | @Table 33 | data class User( 34 | @PrimaryKey 35 | var id: Int, 36 | var username: String, 37 | var age: Int 38 | ) 39 | 40 | fun run() { 41 | try { 42 | val users = database.users.filter { it.id eq 1 } 43 | users.update(User(1, "lucy", 10)) 44 | throw AssertionError("fail") 45 | } catch (_: UnsupportedOperationException) { 46 | } 47 | } 48 | """.trimIndent()) 49 | } 50 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/parser/CompoundPrimaryKeysTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.ksp.compiler.parser 2 | 3 | import org.junit.Test 4 | import org.ktorm.ksp.compiler.BaseKspTest 5 | 6 | class CompoundPrimaryKeysTest : BaseKspTest() { 7 | 8 | @Test 9 | fun `multi primary key`() = runKotlin(""" 10 | @Table(name = "province") 11 | data class Province( 12 | @PrimaryKey 13 | val country:String, 14 | @PrimaryKey 15 | val province:String, 16 | var population:Int 17 | ) 18 | 19 | fun run() { 20 | database.provinces.add(Province("China", "Guangdong", 150000)) 21 | assert(database.provinces.toList().contains(Province("China", "Guangdong", 150000))) 22 | 23 | var province = database.provinces.first { (it.country eq "China") and (it.province eq "Hebei") } 24 | province.population = 200000 25 | database.provinces.update(province) 26 | 27 | province = database.provinces.first { (it.country eq "China") and (it.province eq "Hebei") } 28 | assert(province.population == 200000) 29 | } 30 | """.trimIndent()) 31 | } 32 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/resources/META-INF/services/org.ktorm.ksp.spi.ExtCodeGenerator: -------------------------------------------------------------------------------- 1 | org.ktorm.ksp.compiler.generator.ExtCodeGeneratorTest 2 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/resources/drop-ksp-data.sql: -------------------------------------------------------------------------------- 1 | drop table if exists "user"; 2 | drop table if exists "employee"; 3 | drop table if exists "department"; 4 | drop table if exists "province"; 5 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/resources/init-ksp-data.sql: -------------------------------------------------------------------------------- 1 | create table "user" 2 | ( 3 | "id" int not null primary key auto_increment, 4 | "username" varchar(128) not null, 5 | "age" int not null, 6 | "gender" int not null default 0, 7 | "phone" varchar(128) null 8 | ); 9 | 10 | 11 | insert into "user"("username", "age", "gender") 12 | values ('jack', 20, 0), 13 | ('lucy', 22, 1), 14 | ('mike', 22, 0); 15 | 16 | 17 | create table "employee" 18 | ( 19 | "id" int not null primary key auto_increment, 20 | "name" varchar(128) not null, 21 | "job" varchar(128) not null, 22 | "manager_id" int null, 23 | "hire_date" date not null, 24 | "salary" bigint not null, 25 | "department_id" int not null 26 | ); 27 | 28 | 29 | insert into "employee"("name", "job", "manager_id", "hire_date", "salary", "department_id") 30 | values ('vince', 'engineer', null, '2018-01-01', 100, 1), 31 | ('marry', 'trainee', 1, '2019-01-01', 50, 1), 32 | ('tom', 'director', null, '2018-01-01', 200, 2), 33 | ('penny', 'assistant', 3, '2019-01-01', 100, 2); 34 | 35 | create table "department"( 36 | "id" int not null primary key auto_increment, 37 | "name" varchar(128) not null, 38 | "location" varchar(128) not null, 39 | "mixedCase" varchar(128) 40 | ); 41 | 42 | insert into "department"("name", "location") values ('tech', 'Guangzhou'); 43 | insert into "department"("name", "location") values ('finance', 'Beijing'); 44 | 45 | create table "province" 46 | ( 47 | "country" varchar(128) not null, 48 | "province" varchar(128) not null, 49 | "population" int not null, 50 | primary key ("country", "province") 51 | ); 52 | 53 | insert into "province"("country", "province", "population") 54 | values ('China', 'Hebei', 130000), 55 | ('China', 'Henan', 140000); 56 | -------------------------------------------------------------------------------- /ktorm-ksp-compiler/src/test/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | org.slf4j.simpleLogger.logFile=System.err 2 | org.slf4j.simpleLogger.defaultLogLevel=info 3 | org.slf4j.simpleLogger.log.org.ktorm.database=trace 4 | org.slf4j.simpleLogger.showThreadName=false 5 | org.slf4j.simpleLogger.showLogName=false 6 | org.slf4j.simpleLogger.levelInBrackets=true -------------------------------------------------------------------------------- /ktorm-ksp-spi/ktorm-ksp-spi.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.publish") 5 | id("ktorm.source-header-check") 6 | } 7 | 8 | dependencies { 9 | api("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20") 10 | api("com.squareup:kotlinpoet-ksp:1.11.0") 11 | } 12 | -------------------------------------------------------------------------------- /ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/CodingNamingStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.spi 18 | 19 | import com.google.devtools.ksp.symbol.KSClassDeclaration 20 | import com.google.devtools.ksp.symbol.KSPropertyDeclaration 21 | 22 | /** 23 | * Naming strategy for Kotlin symbols in the generated code. 24 | */ 25 | public interface CodingNamingStrategy { 26 | 27 | /** 28 | * Generate the table class name. 29 | */ 30 | public fun getTableClassName(c: KSClassDeclaration): String 31 | 32 | /** 33 | * Generate the entity sequence name. 34 | */ 35 | public fun getEntitySequenceName(c: KSClassDeclaration): String 36 | 37 | /** 38 | * Generate the column property name. 39 | */ 40 | public fun getColumnPropertyName(c: KSClassDeclaration, prop: KSPropertyDeclaration): String 41 | 42 | /** 43 | * Generate the reference column property name. 44 | */ 45 | public fun getRefColumnPropertyName(c: KSClassDeclaration, prop: KSPropertyDeclaration, ref: TableMetadata): String 46 | 47 | /** 48 | * Generate the name of the referenced table property in the Refs wrapper class. 49 | */ 50 | public fun getRefTablePropertyName(c: KSClassDeclaration, prop: KSPropertyDeclaration, ref: TableMetadata): String 51 | } 52 | -------------------------------------------------------------------------------- /ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/ColumnMetadata.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.spi 18 | 19 | import com.google.devtools.ksp.symbol.KSPropertyDeclaration 20 | import com.google.devtools.ksp.symbol.KSType 21 | 22 | /** 23 | * Column definition metadata. 24 | */ 25 | public data class ColumnMetadata( 26 | 27 | /** 28 | * The annotated entity property of the column. 29 | */ 30 | val entityProperty: KSPropertyDeclaration, 31 | 32 | /** 33 | * The belonging table. 34 | */ 35 | val table: TableMetadata, 36 | 37 | /** 38 | * The name of the column. 39 | */ 40 | val name: String, 41 | 42 | /** 43 | * Check if the column is a primary key. 44 | */ 45 | val isPrimaryKey: Boolean, 46 | 47 | /** 48 | * The SQL type of the column. 49 | */ 50 | val sqlType: KSType, 51 | 52 | /** 53 | * Check if the column is a reference column. 54 | */ 55 | val isReference: Boolean, 56 | 57 | /** 58 | * The referenced table of the column. 59 | */ 60 | val referenceTable: TableMetadata?, 61 | 62 | /** 63 | * The name of the corresponding column property in the generated table class. 64 | */ 65 | val columnPropertyName: String, 66 | 67 | /** 68 | * The name of the corresponding referenced table property in the Refs wrapper class. 69 | */ 70 | val refTablePropertyName: String? 71 | ) 72 | -------------------------------------------------------------------------------- /ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/DatabaseNamingStrategy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.spi 18 | 19 | import com.google.devtools.ksp.symbol.KSClassDeclaration 20 | import com.google.devtools.ksp.symbol.KSPropertyDeclaration 21 | 22 | /** 23 | * Naming strategy for database tables and columns. 24 | */ 25 | public interface DatabaseNamingStrategy { 26 | 27 | /** 28 | * Generate the table name. 29 | */ 30 | public fun getTableName(c: KSClassDeclaration): String 31 | 32 | /** 33 | * Generate the column name. 34 | */ 35 | public fun getColumnName(c: KSClassDeclaration, prop: KSPropertyDeclaration): String 36 | 37 | /** 38 | * Generate the reference column name. 39 | */ 40 | public fun getRefColumnName(c: KSClassDeclaration, prop: KSPropertyDeclaration, ref: TableMetadata): String 41 | } 42 | -------------------------------------------------------------------------------- /ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/ExtCodeGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.spi 18 | 19 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 20 | import com.squareup.kotlinpoet.FunSpec 21 | import com.squareup.kotlinpoet.PropertySpec 22 | import com.squareup.kotlinpoet.TypeSpec 23 | 24 | /** 25 | * Ktorm KSP code generator interface for third-party extensions. 26 | */ 27 | public interface ExtCodeGenerator { 28 | 29 | /** 30 | * Generate types for the [table] in the corresponding resulting file. 31 | */ 32 | public fun generateTypes(table: TableMetadata, environment: SymbolProcessorEnvironment): List { 33 | return emptyList() 34 | } 35 | 36 | /** 37 | * Generate top-level properties for the [table] in the corresponding resulting file. 38 | */ 39 | public fun generateProperties(table: TableMetadata, environment: SymbolProcessorEnvironment): List { 40 | return emptyList() 41 | } 42 | 43 | /** 44 | * Generate top-level functions for the [table] in the corresponding resulting file. 45 | */ 46 | public fun generateFunctions(table: TableMetadata, environment: SymbolProcessorEnvironment): List { 47 | return emptyList() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.ksp.spi 18 | 19 | import com.google.devtools.ksp.symbol.KSClassDeclaration 20 | 21 | /** 22 | * Table definition metadata. 23 | */ 24 | public data class TableMetadata( 25 | 26 | /** 27 | * The annotated entity class of the table. 28 | */ 29 | val entityClass: KSClassDeclaration, 30 | 31 | /** 32 | * The name of the table. 33 | */ 34 | val name: String, 35 | 36 | /** 37 | * The alias of the table. 38 | */ 39 | val alias: String?, 40 | 41 | /** 42 | * The catalog of the table. 43 | */ 44 | val catalog: String?, 45 | 46 | /** 47 | * The schema of the table. 48 | */ 49 | val schema: String?, 50 | 51 | /** 52 | * The name of the corresponding table class in the generated code. 53 | */ 54 | val tableClassName: String, 55 | 56 | /** 57 | * The name of the corresponding entity sequence in the generated code. 58 | */ 59 | val entitySequenceName: String, 60 | 61 | /** 62 | * Properties that should be ignored for generating column definitions. 63 | */ 64 | val ignoreProperties: Set, 65 | 66 | /** 67 | * Columns in the table. 68 | */ 69 | val columns: List 70 | ) 71 | -------------------------------------------------------------------------------- /ktorm-support-mysql/ktorm-support-mysql.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.modularity") 5 | id("ktorm.publish") 6 | id("ktorm.source-header-check") 7 | } 8 | 9 | dependencies { 10 | api(project(":ktorm-core")) 11 | testImplementation(project(":ktorm-core", configuration = "testOutput")) 12 | testImplementation(project(":ktorm-jackson")) 13 | testImplementation("org.testcontainers:mysql:1.19.7") 14 | testImplementation("mysql:mysql-connector-java:8.0.23") 15 | } 16 | -------------------------------------------------------------------------------- /ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/DefaultValue.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | @file:Suppress("MatchingDeclarationName") 18 | 19 | package org.ktorm.support.mysql 20 | 21 | import org.ktorm.expression.ScalarExpression 22 | import org.ktorm.schema.Column 23 | import org.ktorm.schema.SqlType 24 | import java.util.Collections.emptyMap 25 | 26 | /** 27 | * Default value expression, translated to the `default` keyword in MySQL, used in insert statements. 28 | * 29 | * For example: 30 | * 31 | * ```sql 32 | * insert into table (column1, column2) values (default, ?) 33 | * ``` 34 | */ 35 | public data class DefaultValueExpression( 36 | override val sqlType: SqlType, 37 | override val isLeafNode: Boolean = true, 38 | override val extraProperties: Map = emptyMap() 39 | ) : ScalarExpression() 40 | 41 | /** 42 | * Return a default value for [this] column, see [DefaultValueExpression]. 43 | */ 44 | public fun Column.defaultValue(): DefaultValueExpression = DefaultValueExpression(this.sqlType) 45 | -------------------------------------------------------------------------------- /ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/MatchAgainst.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.support.mysql 18 | 19 | import org.ktorm.expression.ColumnExpression 20 | import org.ktorm.expression.ScalarExpression 21 | import org.ktorm.schema.BooleanSqlType 22 | import org.ktorm.schema.Column 23 | import org.ktorm.schema.SqlType 24 | 25 | /** 26 | * Enum class represents search modifiers in MySQL `match ... against ...` expressions. 27 | * See https://dev.mysql.com/doc/refman/5.5/en/fulltext-search.html 28 | */ 29 | public enum class SearchModifier(private val value: String) { 30 | 31 | /** 32 | * Search modifier `in natural language mode`. 33 | */ 34 | IN_NATURAL_LANGUAGE_MODE("in natural language mode"), 35 | 36 | /** 37 | * Search modifier `in natural language mode with query expansion`. 38 | */ 39 | IN_NATURAL_LANGUAGE_MODE_WITH_QUERY_EXPANSION("in natural language mode with query expansion"), 40 | 41 | /** 42 | * Search modifier `in boolean mode`. 43 | */ 44 | IN_BOOLEAN_MODE("in boolean mode"), 45 | 46 | /** 47 | * Search modifier `with query expansion`. 48 | */ 49 | WITH_QUERY_EXPANSION("with query expansion"); 50 | 51 | override fun toString(): String { 52 | return value 53 | } 54 | } 55 | 56 | /** 57 | * Match against expression, represents an `match ... against ...` operation in MySQL. 58 | * See https://dev.mysql.com/doc/refman/5.5/en/insert-on-duplicate.html 59 | * 60 | * @property matchColumns columns to be searched. 61 | * @property searchString the search string. 62 | * @property searchModifier optional modifier that indicates what type of search to perform. 63 | */ 64 | public data class MatchAgainstExpression( 65 | val matchColumns: List>, 66 | val searchString: String, 67 | val searchModifier: SearchModifier? = null, 68 | override val sqlType: SqlType = BooleanSqlType, 69 | override val isLeafNode: Boolean = false, 70 | override val extraProperties: Map = emptyMap() 71 | ) : ScalarExpression() 72 | 73 | /** 74 | * Intermediate class that wraps the search columns of a [MatchAgainstExpression]. 75 | */ 76 | public class MatchColumns(columns: List>) : List> by columns 77 | 78 | /** 79 | * Return an intermediate object that wraps the columns to be searched. We can continue to call [against] on the 80 | * returned object to create a [MatchAgainstExpression] that searches the wrapped columns. 81 | */ 82 | public fun match(vararg columns: Column<*>): MatchColumns { 83 | return MatchColumns(columns.map { it.asExpression() }) 84 | } 85 | 86 | /** 87 | * Create a [MatchAgainstExpression] that searches on the current [MatchColumns]. 88 | * Translated to `match (col1, col2) against (searchString modifier)` in SQL. 89 | */ 90 | public fun MatchColumns.against(searchString: String, modifier: SearchModifier? = null): MatchAgainstExpression { 91 | return MatchAgainstExpression(this, searchString, modifier) 92 | } 93 | -------------------------------------------------------------------------------- /ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/MySqlDialect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.support.mysql 18 | 19 | import org.ktorm.database.Database 20 | import org.ktorm.database.SqlDialect 21 | import org.ktorm.expression.SqlExpressionVisitor 22 | import org.ktorm.expression.SqlExpressionVisitorInterceptor 23 | import org.ktorm.expression.SqlFormatter 24 | import org.ktorm.expression.newVisitorInstance 25 | 26 | /** 27 | * [SqlDialect] implementation for MySQL database. 28 | */ 29 | public open class MySqlDialect : SqlDialect { 30 | 31 | override fun createExpressionVisitor(interceptor: SqlExpressionVisitorInterceptor): SqlExpressionVisitor { 32 | return MySqlExpressionVisitor::class.newVisitorInstance(interceptor) 33 | } 34 | 35 | override fun createSqlFormatter(database: Database, beautifySql: Boolean, indentSize: Int): SqlFormatter { 36 | return MySqlFormatter(database, beautifySql, indentSize) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ktorm-support-mysql/src/main/kotlin/org/ktorm/support/mysql/NaturalJoin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | @file:Suppress("MatchingDeclarationName") 18 | 19 | package org.ktorm.support.mysql 20 | 21 | import org.ktorm.dsl.QuerySource 22 | import org.ktorm.expression.QuerySourceExpression 23 | import org.ktorm.schema.BaseTable 24 | 25 | /** 26 | * MySQL natural join expression. 27 | * 28 | * @property left the left table. 29 | * @property right the right table. 30 | */ 31 | public data class NaturalJoinExpression( 32 | val left: QuerySourceExpression, 33 | val right: QuerySourceExpression, 34 | override val isLeafNode: Boolean = false, 35 | override val extraProperties: Map = emptyMap() 36 | ) : QuerySourceExpression() 37 | 38 | /** 39 | * Join the right table and return a new [QuerySource], translated to `natural join` in SQL. 40 | * 41 | * @since 2.7 42 | */ 43 | public fun QuerySource.naturalJoin(right: BaseTable<*>): QuerySource { 44 | return this.copy(expression = NaturalJoinExpression(left = expression, right = right.asExpression())) 45 | } 46 | -------------------------------------------------------------------------------- /ktorm-support-mysql/src/main/moditect/module-info.java: -------------------------------------------------------------------------------- 1 | module ktorm.support.mysql { 2 | requires ktorm.core; 3 | exports org.ktorm.support.mysql; 4 | provides org.ktorm.database.SqlDialect with org.ktorm.support.mysql.MySqlDialect; 5 | } 6 | -------------------------------------------------------------------------------- /ktorm-support-mysql/src/main/resources/META-INF/services/org.ktorm.database.SqlDialect: -------------------------------------------------------------------------------- 1 | org.ktorm.support.mysql.MySqlDialect -------------------------------------------------------------------------------- /ktorm-support-mysql/src/test/kotlin/org/ktorm/support/mysql/BaseMySqlTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.mysql 2 | 3 | import org.ktorm.BaseTest 4 | import org.ktorm.database.Database 5 | import org.testcontainers.containers.MySQLContainer 6 | import kotlin.concurrent.thread 7 | 8 | abstract class BaseMySqlTest : BaseTest() { 9 | 10 | override fun init() { 11 | database = Database.connect(jdbcUrl, driverClassName, username, password) 12 | execSqlScript("init-mysql-data.sql") 13 | } 14 | 15 | override fun destroy() { 16 | execSqlScript("drop-mysql-data.sql") 17 | } 18 | 19 | companion object : MySQLContainer("mysql:8") { 20 | init { 21 | // Start the container when it's first used. 22 | start() 23 | // Stop the container when the process exits. 24 | Runtime.getRuntime().addShutdownHook(thread(start = false) { stop() }) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /ktorm-support-mysql/src/test/kotlin/org/ktorm/support/mysql/DefaultValueTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.mysql 2 | 3 | import org.junit.Test 4 | import org.ktorm.database.Database 5 | import org.ktorm.dsl.eq 6 | import org.ktorm.dsl.inList 7 | import org.ktorm.dsl.insertAndGenerateKey 8 | import org.ktorm.entity.* 9 | import org.ktorm.schema.Table 10 | import org.ktorm.schema.int 11 | import org.ktorm.schema.varchar 12 | import kotlin.test.assertEquals 13 | import kotlin.test.assertIs 14 | import kotlin.test.assertNotNull 15 | import kotlin.test.assertNull 16 | 17 | class DefaultValueTest : BaseMySqlTest() { 18 | 19 | object Users : Table("t_user") { 20 | val id = int("id").primaryKey().bindTo { it.id } 21 | val username = varchar("username").bindTo { it.username } 22 | val age = int("age").bindTo { it.age } 23 | } 24 | 25 | interface User : Entity { 26 | var id: Int 27 | var username: String 28 | var age: Int? 29 | } 30 | 31 | private val Database.users: EntitySequence get() = this.sequenceOf(Users) 32 | 33 | @Test 34 | fun insert() { 35 | val id = database.insertAndGenerateKey(Users) { 36 | set(it.id, it.id.defaultValue()) 37 | set(it.username, it.username.defaultValue()) 38 | set(it.age, it.age.defaultValue()) 39 | } 40 | assertIs(id) 41 | val entity = database.users.firstOrNull { it.id eq id } 42 | assertNotNull(entity) 43 | assertNotNull(entity.id) 44 | assertEquals(entity.username, "default") 45 | assertNull(entity.age) 46 | } 47 | 48 | @Test 49 | fun bulkInsert() { 50 | database.bulkInsert(Users) { 51 | item { 52 | set(it.id, 10) 53 | set(it.username, it.username.defaultValue()) 54 | set(it.age, 10) 55 | } 56 | item { 57 | set(it.id, 11) 58 | set(it.username, it.username.defaultValue()) 59 | set(it.age, 11) 60 | } 61 | } 62 | val defaultValues = database.users.filter { it.id inList (10..11).toList() }.toList() 63 | assertEquals(defaultValues.size, 2) 64 | for (defaultValue in defaultValues) { 65 | assertEquals(defaultValue.username, "default") 66 | } 67 | 68 | database.bulkInsertOrUpdate(Users) { 69 | item { 70 | set(it.id, 10) 71 | set(it.age, 10) 72 | } 73 | onDuplicateKey { 74 | set(it.age, it.age.defaultValue()) 75 | } 76 | } 77 | val user = database.users.first { it.id eq 10 } 78 | assertNull(user.age) 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /ktorm-support-mysql/src/test/kotlin/org/ktorm/support/mysql/QueryTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.mysql 2 | 3 | import org.junit.Test 4 | import org.ktorm.dsl.* 5 | import kotlin.test.assertContentEquals 6 | import kotlin.test.assertEquals 7 | 8 | class QueryTest : BaseMySqlTest() { 9 | 10 | @Test 11 | fun testCast() { 12 | val salaries = database 13 | .from(Employees) 14 | .select(Employees.salary.toFloat()) 15 | .where { Employees.salary eq 200 } 16 | .map { row -> row.getObject(1) } 17 | 18 | assertContentEquals(listOf(200.0F), salaries) 19 | } 20 | 21 | @Test 22 | fun testUnion() { 23 | val query = database 24 | .from(Employees) 25 | .select(Employees.id) 26 | .union( 27 | database.from(Departments).select(Departments.id) 28 | ) 29 | .union( 30 | database.from(Departments).select(Departments.id) 31 | ) 32 | .orderBy(Employees.id.desc()) 33 | .limit(0, 4) 34 | 35 | println(query.sql) 36 | 37 | val results = query.joinToString { row -> row.getString(1).orEmpty() } 38 | assertEquals("4, 3, 2, 1", results) 39 | } 40 | 41 | @Test 42 | fun testUnionAll() { 43 | val query = database 44 | .from(Employees) 45 | .select(Employees.id) 46 | .unionAll( 47 | database.from(Departments).select(Departments.id) 48 | ) 49 | .unionAll( 50 | database.from(Departments).select(Departments.id) 51 | ) 52 | .orderBy(Employees.id.desc()) 53 | 54 | println(query.sql) 55 | 56 | val results = query.joinToString { row -> row.getString(1).orEmpty() } 57 | assertEquals("4, 3, 2, 2, 2, 1, 1, 1", results) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ktorm-support-mysql/src/test/resources/drop-mysql-data.sql: -------------------------------------------------------------------------------- 1 | drop table if exists t_department; 2 | drop table if exists t_employee; 3 | drop table if exists t_user; 4 | drop table if exists t_multi_generated_key; 5 | -------------------------------------------------------------------------------- /ktorm-support-mysql/src/test/resources/init-mysql-data.sql: -------------------------------------------------------------------------------- 1 | create table t_department( 2 | id int not null primary key auto_increment, 3 | name varchar(128) not null, 4 | location varchar(128) not null, 5 | mixedCase varchar(128) 6 | ); 7 | 8 | create table t_employee( 9 | id int not null primary key auto_increment, 10 | name varchar(128) not null, 11 | job varchar(128) not null, 12 | manager_id int null, 13 | hire_date date not null, 14 | salary bigint not null, 15 | department_id int not null 16 | ); 17 | 18 | create fulltext index employee_name_job on t_employee(name, job); 19 | 20 | create table t_user( 21 | id int not null primary key auto_increment, 22 | username varchar(128) default 'default', 23 | age int 24 | ); 25 | 26 | create table t_multi_generated_key( 27 | id int not null primary key auto_increment, 28 | k varchar(128) default (uuid()), 29 | v varchar(128) 30 | ); 31 | 32 | insert into t_department(name, location) values ('tech', 'Guangzhou'); 33 | insert into t_department(name, location) values ('finance', 'Beijing'); 34 | 35 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 36 | values ('vince', 'engineer', null, '2018-01-01', 100, 1); 37 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 38 | values ('marry', 'trainee', 1, '2019-01-01', 50, 1); 39 | 40 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 41 | values ('tom', 'director', null, '2018-01-01', 200, 2); 42 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 43 | values ('penny', 'assistant', 3, '2019-01-01', 100, 2); 44 | 45 | 46 | -------------------------------------------------------------------------------- /ktorm-support-oracle/ktorm-support-oracle.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.modularity") 5 | id("ktorm.publish") 6 | id("ktorm.source-header-check") 7 | } 8 | 9 | dependencies { 10 | api(project(":ktorm-core")) 11 | testImplementation(project(":ktorm-core", configuration = "testOutput")) 12 | testImplementation("org.testcontainers:oracle-xe:1.19.7") 13 | testImplementation(files("lib/ojdbc6-11.2.0.3.jar")) 14 | } 15 | -------------------------------------------------------------------------------- /ktorm-support-oracle/lib/ojdbc6-11.2.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kotlin-orm/ktorm/601d9a11f45f33bb3d8f637162f6650236a84e89/ktorm-support-oracle/lib/ojdbc6-11.2.0.3.jar -------------------------------------------------------------------------------- /ktorm-support-oracle/src/main/kotlin/org/ktorm/support/oracle/OracleDialect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.support.oracle 18 | 19 | import org.ktorm.database.Database 20 | import org.ktorm.database.SqlDialect 21 | import org.ktorm.expression.SqlFormatter 22 | 23 | /** 24 | * [SqlDialect] implementation for Oracle database. 25 | */ 26 | public open class OracleDialect : SqlDialect { 27 | 28 | override fun createSqlFormatter(database: Database, beautifySql: Boolean, indentSize: Int): SqlFormatter { 29 | return OracleFormatter(database, beautifySql, indentSize) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ktorm-support-oracle/src/main/moditect/module-info.java: -------------------------------------------------------------------------------- 1 | module ktorm.support.oracle { 2 | requires ktorm.core; 3 | exports org.ktorm.support.oracle; 4 | provides org.ktorm.database.SqlDialect with org.ktorm.support.oracle.OracleDialect; 5 | } 6 | -------------------------------------------------------------------------------- /ktorm-support-oracle/src/main/resources/META-INF/services/org.ktorm.database.SqlDialect: -------------------------------------------------------------------------------- 1 | org.ktorm.support.oracle.OracleDialect -------------------------------------------------------------------------------- /ktorm-support-oracle/src/test/kotlin/org/ktorm/support/oracle/BaseOracleTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.oracle 2 | 3 | import org.junit.ClassRule 4 | import org.ktorm.BaseTest 5 | import org.ktorm.database.Database 6 | import org.testcontainers.containers.OracleContainer 7 | 8 | abstract class BaseOracleTest : BaseTest() { 9 | 10 | override fun init() { 11 | database = Database.connect(jdbcUrl, driverClassName, username, password, alwaysQuoteIdentifiers = true) 12 | execSqlScript("init-oracle-data.sql") 13 | } 14 | 15 | override fun destroy() { 16 | execSqlScript("drop-oracle-data.sql") 17 | } 18 | 19 | /** 20 | * Unfortunately Oracle databases aren’t compatible with the new Apple Silicon CPU architecture, 21 | * so if you are using a brand-new MacBook, you need to install colima. 22 | * 23 | * 1. Installation: https://github.com/abiosoft/colima#installation 24 | * 2. Run Colima with the command: `colima start --arch x86_64 --cpu 2 --memory 4 --disk 16 --network-address` 25 | * 3. Set env vars like below: 26 | * 27 | * ```sh 28 | * export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock 29 | * export TESTCONTAINERS_HOST_OVERRIDE=$(colima ls -j | jq -r '.address') 30 | * export DOCKER_HOST="unix://${HOME}/.colima/default/docker.sock" 31 | * ``` 32 | * 33 | * See https://java.testcontainers.org/supported_docker_environment/#colima 34 | */ 35 | companion object { 36 | @JvmField 37 | @ClassRule 38 | val container: OracleContainer 39 | = OracleContainer("gvenzl/oracle-xe:11.2.0.2") 40 | .usingSid() 41 | .withCreateContainerCmdModifier { cmd -> cmd.hostConfig?.withShmSize((1 * 1024 * 1024 * 1024).toLong()) } 42 | 43 | val jdbcUrl: String get() = container.jdbcUrl 44 | 45 | val driverClassName: String get() = container.driverClassName 46 | 47 | val username: String get() = container.username 48 | 49 | val password: String get() = container.password 50 | } 51 | } -------------------------------------------------------------------------------- /ktorm-support-oracle/src/test/kotlin/org/ktorm/support/oracle/QueryTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.oracle 2 | 3 | import org.junit.Test 4 | import org.ktorm.dsl.* 5 | import org.ktorm.schema.IntSqlType 6 | import java.math.BigDecimal 7 | import kotlin.test.assertContentEquals 8 | import kotlin.test.assertEquals 9 | 10 | class QueryTest : BaseOracleTest() { 11 | 12 | @Test 13 | fun testCast() { 14 | val mixedCases = database 15 | .from(Departments) 16 | .select(Departments.mixedCase.cast(IntSqlType)) 17 | .where { Departments.mixedCase eq "123" } 18 | .map { row -> row.getObject(1)} 19 | 20 | assertContentEquals(listOf(BigDecimal(123)), mixedCases) 21 | } 22 | 23 | @Test 24 | fun testUnion() { 25 | val query = database 26 | .from(Employees) 27 | .select(Employees.id) 28 | .union( 29 | database.from(Departments).select(Departments.id) 30 | ) 31 | .union( 32 | database.from(Departments).select(Departments.id) 33 | ) 34 | .orderBy(Employees.id.desc()) 35 | .limit(0, 4) 36 | 37 | println(query.sql) 38 | 39 | val results = query.joinToString { row -> row.getString(1).orEmpty() } 40 | assertEquals("4, 3, 2, 1", results) 41 | } 42 | 43 | @Test 44 | fun testUnionWithoutOrderBy() { 45 | val query = database 46 | .from(Employees) 47 | .select(Employees.id) 48 | .union( 49 | database.from(Departments).select(Departments.id) 50 | ) 51 | .union( 52 | database.from(Departments).select(Departments.id) 53 | ) 54 | 55 | println(query.sql) 56 | 57 | val results = query.map { row -> row.getInt(1) } 58 | assertEquals(setOf(1, 2, 3, 4), results.toSet()) 59 | } 60 | 61 | @Test 62 | fun testUnionAll() { 63 | val query = database 64 | .from(Employees) 65 | .select(Employees.id) 66 | .unionAll( 67 | database.from(Departments).select(Departments.id) 68 | ) 69 | .unionAll( 70 | database.from(Departments).select(Departments.id) 71 | ) 72 | .orderBy(Employees.id.desc()) 73 | 74 | println(query.sql) 75 | 76 | val results = query.joinToString { row -> row.getString(1).orEmpty() } 77 | assertEquals("4, 3, 3, 3, 2, 2, 2, 1, 1, 1", results) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /ktorm-support-oracle/src/test/resources/drop-oracle-data.sql: -------------------------------------------------------------------------------- 1 | drop table "t_department"; 2 | drop table "t_employee"; 3 | drop table "t_employee0"; -------------------------------------------------------------------------------- /ktorm-support-oracle/src/test/resources/init-oracle-data.sql: -------------------------------------------------------------------------------- 1 | create table "t_department"( 2 | "id" int not null primary key, 3 | "name" varchar(128) not null, 4 | "location" varchar(128) not null, 5 | "mixedCase" varchar(128) 6 | ); 7 | 8 | create table "t_employee"( 9 | "id" int not null primary key, 10 | "name" varchar(128) not null, 11 | "job" varchar(128) not null, 12 | "manager_id" int null, 13 | "hire_date" date not null, 14 | "salary" int not null, 15 | "department_id" int not null 16 | ); 17 | 18 | insert into "t_department"("id", "name", "location") values (1, 'tech', 'Guangzhou'); 19 | insert into "t_department"("id", "name", "location") values (2, 'finance', 'Beijing'); 20 | insert into "t_department"("id", "name", "location", "mixedCase") values (3, 'ai', 'Hamburg', '123'); 21 | 22 | insert into "t_employee"("id", "name", "job", "manager_id", "hire_date", "salary", "department_id") 23 | values (1, 'vince', 'engineer', null, to_date('2018-01-01', 'yyyy-MM-dd'), 100, 1); 24 | insert into "t_employee"("id", "name", "job", "manager_id", "hire_date", "salary", "department_id") 25 | values (2, 'marry', 'trainee', 1, to_date('2019-01-01', 'yyyy-MM-dd'), 50, 1); 26 | 27 | insert into "t_employee"("id", "name", "job", "manager_id", "hire_date", "salary", "department_id") 28 | values (3, 'tom', 'director', null, to_date('2018-01-01', 'yyyy-MM-dd'), 200, 2); 29 | insert into "t_employee"("id", "name", "job", "manager_id", "hire_date", "salary", "department_id") 30 | values (4, 'penny', 'assistant', 3, to_date('2019-01-01', 'yyyy-MM-dd'), 100, 2); 31 | 32 | 33 | 34 | create table "t_employee0"( 35 | "id" int not null primary key, 36 | "name" varchar(128) not null, 37 | "job" varchar(128) not null, 38 | "manager_id" int null, 39 | "hire_date" date not null, 40 | "salary" int not null, 41 | "department_id" int not null 42 | ); 43 | 44 | insert into "t_employee0"("id", "name", "job", "manager_id", "hire_date", "salary", "department_id") 45 | values (1, 'vince', 'engineer', null, to_date('2018-01-01', 'yyyy-MM-dd'), 100, 1); 46 | insert into "t_employee0"("id", "name", "job", "manager_id", "hire_date", "salary", "department_id") 47 | values (2, 'marry', 'trainee', 1, to_date('2019-01-01', 'yyyy-MM-dd'), 50, 1); 48 | 49 | insert into "t_employee0"("id", "name", "job", "manager_id", "hire_date", "salary", "department_id") 50 | values (3, 'tom', 'director', null, to_date('2018-01-01', 'yyyy-MM-dd'), 200, 2); 51 | insert into "t_employee0"("id", "name", "job", "manager_id", "hire_date", "salary", "department_id") 52 | values (4, 'penny', 'assistant', 3, to_date('2019-01-01', 'yyyy-MM-dd'), 100, 2); 53 | -------------------------------------------------------------------------------- /ktorm-support-postgresql/ktorm-support-postgresql.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.modularity") 5 | id("ktorm.publish") 6 | id("ktorm.source-header-check") 7 | } 8 | 9 | dependencies { 10 | api(project(":ktorm-core")) 11 | testImplementation(project(":ktorm-core", configuration = "testOutput")) 12 | testImplementation(project(":ktorm-jackson")) 13 | testImplementation("org.testcontainers:postgresql:1.19.7") 14 | testImplementation("org.postgresql:postgresql:42.2.5") 15 | testImplementation("com.zaxxer:HikariCP:4.0.3") 16 | testImplementation("com.mchange:c3p0:0.9.5.5") 17 | testImplementation("org.apache.commons:commons-dbcp2:2.8.0") 18 | testImplementation("com.alibaba:druid:1.2.6") 19 | } 20 | -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/DefaultValue.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | @file:Suppress("MatchingDeclarationName") 18 | 19 | package org.ktorm.support.postgresql 20 | 21 | import org.ktorm.expression.ScalarExpression 22 | import org.ktorm.schema.Column 23 | import org.ktorm.schema.SqlType 24 | import java.util.Collections.emptyMap 25 | 26 | /** 27 | * Default value expression, translated to the `default` keyword in PostgreSQL, used in insert statements. 28 | * 29 | * For example: 30 | * 31 | * ```sql 32 | * insert into table (column1, column2) values (default, ?) 33 | * ``` 34 | */ 35 | public data class DefaultValueExpression( 36 | override val sqlType: SqlType, 37 | override val isLeafNode: Boolean = true, 38 | override val extraProperties: Map = emptyMap() 39 | ) : ScalarExpression() 40 | 41 | /** 42 | * Return a default value for [this] column, see [DefaultValueExpression]. 43 | */ 44 | public fun Column.defaultValue(): DefaultValueExpression = DefaultValueExpression(this.sqlType) 45 | -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/ILike.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | @file:Suppress("MatchingDeclarationName") 18 | 19 | package org.ktorm.support.postgresql 20 | 21 | import org.ktorm.expression.ArgumentExpression 22 | import org.ktorm.expression.ScalarExpression 23 | import org.ktorm.schema.BooleanSqlType 24 | import org.ktorm.schema.ColumnDeclaring 25 | import org.ktorm.schema.SqlType 26 | import org.ktorm.schema.VarcharSqlType 27 | 28 | /** 29 | * ILike expression, represents PostgreSQL `ilike` keyword. 30 | * 31 | * @property left the expression's left operand. 32 | * @property right the expression's right operand. 33 | */ 34 | public data class ILikeExpression( 35 | val left: ScalarExpression<*>, 36 | val right: ScalarExpression<*>, 37 | override val sqlType: SqlType = BooleanSqlType, 38 | override val isLeafNode: Boolean = false, 39 | override val extraProperties: Map = emptyMap() 40 | ) : ScalarExpression() 41 | 42 | /** 43 | * ILike operator, translated to the `ilike` keyword in PostgreSQL. 44 | */ 45 | public infix fun ColumnDeclaring<*>.ilike(expr: ColumnDeclaring): ILikeExpression { 46 | return ILikeExpression(asExpression(), expr.asExpression()) 47 | } 48 | 49 | /** 50 | * ILike operator, translated to the `ilike` keyword in PostgreSQL. 51 | */ 52 | public infix fun ColumnDeclaring<*>.ilike(argument: String): ILikeExpression { 53 | return this ilike ArgumentExpression(argument, VarcharSqlType) 54 | } 55 | -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/PostgreSqlDialect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.support.postgresql 18 | 19 | import org.ktorm.database.Database 20 | import org.ktorm.database.SqlDialect 21 | import org.ktorm.expression.SqlExpressionVisitor 22 | import org.ktorm.expression.SqlExpressionVisitorInterceptor 23 | import org.ktorm.expression.SqlFormatter 24 | import org.ktorm.expression.newVisitorInstance 25 | 26 | /** 27 | * [SqlDialect] implementation for PostgreSQL database. 28 | */ 29 | public open class PostgreSqlDialect : SqlDialect { 30 | 31 | override fun createExpressionVisitor(interceptor: SqlExpressionVisitorInterceptor): SqlExpressionVisitor { 32 | return PostgreSqlExpressionVisitor::class.newVisitorInstance(interceptor) 33 | } 34 | 35 | override fun createSqlFormatter(database: Database, beautifySql: Boolean, indentSize: Int): SqlFormatter { 36 | return PostgreSqlFormatter(database, beautifySql, indentSize) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/main/moditect/module-info.java: -------------------------------------------------------------------------------- 1 | module ktorm.support.postgresql { 2 | requires ktorm.core; 3 | exports org.ktorm.support.postgresql; 4 | provides org.ktorm.database.SqlDialect with org.ktorm.support.postgresql.PostgreSqlDialect; 5 | } 6 | -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/main/resources/META-INF/services/org.ktorm.database.SqlDialect: -------------------------------------------------------------------------------- 1 | org.ktorm.support.postgresql.PostgreSqlDialect -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/ArraysTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.postgresql 2 | 3 | import org.junit.Test 4 | import org.ktorm.dsl.* 5 | import org.ktorm.entity.tupleOf 6 | import org.ktorm.schema.Table 7 | import org.ktorm.schema.int 8 | 9 | /** 10 | * Created by vince at Sep 09, 2023. 11 | */ 12 | class ArraysTest : BasePostgreSqlTest() { 13 | 14 | object Arrays : Table("t_array") { 15 | val id = int("id").primaryKey() 16 | val shorts = shortArray("shorts") 17 | val ints = intArray("ints") 18 | val longs = longArray("longs") 19 | val floats = floatArray("floats") 20 | val doubles = doubleArray("doubles") 21 | val booleans = booleanArray("booleans") 22 | val texts = textArray("texts") 23 | } 24 | 25 | @Test 26 | fun testArrays() { 27 | database.insert(Arrays) { 28 | set(it.shorts, shortArrayOf(1, 2, 3, 4)) 29 | set(it.ints, intArrayOf(1, 2, 3, 4)) 30 | set(it.longs, longArrayOf(1, 2, 3, 4)) 31 | set(it.floats, floatArrayOf(1.0F, 2.0F, 3.0F, 4.0F)) 32 | set(it.doubles, doubleArrayOf(1.0, 2.0, 3.0, 4.0)) 33 | set(it.booleans, booleanArrayOf(false, true)) 34 | set(it.texts, arrayOf("1", "2", "3", "4")) 35 | } 36 | 37 | val results = database 38 | .from(Arrays) 39 | .select(Arrays.columns) 40 | .where(Arrays.id eq 1) 41 | .map { row -> 42 | tupleOf( 43 | row[Arrays.shorts], 44 | row[Arrays.ints], 45 | row[Arrays.longs], 46 | row[Arrays.floats], 47 | row[Arrays.doubles], 48 | row[Arrays.booleans], 49 | row[Arrays.texts] 50 | ) 51 | } 52 | 53 | val (shorts, ints, longs, floats, doubles, booleans, texts) = results[0] 54 | assert(shorts.contentEquals(shortArrayOf(1, 2, 3, 4))) 55 | assert(ints.contentEquals(intArrayOf(1, 2, 3, 4))) 56 | assert(longs.contentEquals(longArrayOf(1, 2, 3, 4))) 57 | assert(floats.contentEquals(floatArrayOf(1.0F, 2.0F, 3.0F, 4.0F))) 58 | assert(doubles.contentEquals(doubleArrayOf(1.0, 2.0, 3.0, 4.0))) 59 | assert(booleans.contentEquals(booleanArrayOf(false, true))) 60 | assert(texts.contentEquals(arrayOf("1", "2", "3", "4"))) 61 | } 62 | } -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/BasePostgreSqlTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.postgresql 2 | 3 | import org.ktorm.BaseTest 4 | import org.ktorm.database.Database 5 | import org.testcontainers.containers.PostgreSQLContainer 6 | import kotlin.concurrent.thread 7 | 8 | abstract class BasePostgreSqlTest : BaseTest() { 9 | 10 | override fun init() { 11 | database = Database.connect(jdbcUrl, driverClassName, username, password) 12 | execSqlScript("init-postgresql-data.sql") 13 | } 14 | 15 | override fun destroy() { 16 | execSqlScript("drop-postgresql-data.sql") 17 | } 18 | 19 | companion object : PostgreSQLContainer("postgres:14-alpine") { 20 | init { 21 | // Start the container when it's first used. 22 | start() 23 | // Stop the container when the process exits. 24 | Runtime.getRuntime().addShutdownHook(thread(start = false) { stop() }) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/ConnectionPoolTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.postgresql 2 | 3 | import com.alibaba.druid.pool.DruidDataSource 4 | import com.mchange.v2.c3p0.ComboPooledDataSource 5 | import com.zaxxer.hikari.HikariDataSource 6 | import org.apache.commons.dbcp2.BasicDataSource 7 | import org.junit.Test 8 | import org.ktorm.database.Database 9 | import org.ktorm.dsl.forEach 10 | import org.ktorm.dsl.from 11 | import org.ktorm.dsl.insert 12 | import org.ktorm.dsl.select 13 | import org.ktorm.jackson.json 14 | import org.ktorm.schema.Table 15 | 16 | @Suppress("ConvertTryFinallyToUseCall") 17 | class ConnectionPoolTest : BasePostgreSqlTest() { 18 | 19 | @Test 20 | fun testJsonWithDefaultConnection() { 21 | testJson(database) 22 | } 23 | 24 | @Test 25 | fun testJsonWithHikariCP() { 26 | val ds = HikariDataSource() 27 | ds.jdbcUrl = jdbcUrl 28 | ds.driverClassName = driverClassName 29 | ds.username = username 30 | ds.password = password 31 | 32 | try { 33 | testJson(Database.connect(ds)) 34 | } finally { 35 | ds.close() 36 | } 37 | } 38 | 39 | @Test 40 | fun testJsonWithC3P0() { 41 | val ds = ComboPooledDataSource() 42 | ds.jdbcUrl = jdbcUrl 43 | ds.driverClass = driverClassName 44 | ds.user = username 45 | ds.password = password 46 | 47 | try { 48 | testJson(Database.connect(ds)) 49 | } finally { 50 | ds.close() 51 | } 52 | } 53 | 54 | @Test 55 | fun testJsonWithDBCP() { 56 | val ds = BasicDataSource() 57 | ds.url = jdbcUrl 58 | ds.driverClassName = driverClassName 59 | ds.username = username 60 | ds.password = password 61 | 62 | try { 63 | testJson(Database.connect(ds)) 64 | } finally { 65 | ds.close() 66 | } 67 | } 68 | 69 | @Test 70 | fun testJsonWithDruid() { 71 | val ds = DruidDataSource() 72 | ds.url = jdbcUrl 73 | ds.driverClassName = driverClassName 74 | ds.username = username 75 | ds.password = password 76 | 77 | try { 78 | testJson(Database.connect(ds)) 79 | } finally { 80 | ds.close() 81 | } 82 | } 83 | 84 | private fun testJson(database: Database) { 85 | val t = object : Table("t_json") { 86 | val obj = json("obj") 87 | val arr = json>("arr") 88 | } 89 | 90 | database.insert(t) { 91 | set(it.obj, Employee { name = "vince"; salary = 100 }) 92 | set(it.arr, listOf(1, 2, 3)) 93 | } 94 | 95 | database.insert(t) { 96 | set(it.obj, null) 97 | set(it.arr, null) 98 | } 99 | 100 | database 101 | .from(t) 102 | .select(t.obj, t.arr) 103 | .forEach { row -> 104 | println("${row.getString(1)}:${row.getString(2)}") 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/DefaultValueTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.postgresql 2 | 3 | import org.junit.Test 4 | import org.ktorm.database.Database 5 | import org.ktorm.dsl.eq 6 | import org.ktorm.dsl.inList 7 | import org.ktorm.entity.* 8 | import org.ktorm.schema.Table 9 | import org.ktorm.schema.int 10 | import org.ktorm.schema.varchar 11 | import kotlin.test.assertEquals 12 | import kotlin.test.assertNotNull 13 | import kotlin.test.assertNull 14 | 15 | class DefaultValueTest : BasePostgreSqlTest() { 16 | 17 | object Users : Table("t_user") { 18 | val id = int("id").primaryKey().bindTo { it.id } 19 | val username = varchar("username").bindTo { it.username } 20 | val age = int("age").bindTo { it.age } 21 | } 22 | 23 | interface User : Entity { 24 | var id: Int 25 | var username: String 26 | var age: Int? 27 | } 28 | 29 | private val Database.users: EntitySequence get() = this.sequenceOf(Users) 30 | 31 | @Test 32 | fun insert() { 33 | val id = database.insertReturning(Users, returning = Users.id) { 34 | set(it.id, it.id.defaultValue()) 35 | set(it.username, it.username.defaultValue()) 36 | set(it.age, it.age.defaultValue()) 37 | } 38 | assertNotNull(id) 39 | val entity = database.users.firstOrNull { it.id eq id } 40 | assertNotNull(entity) 41 | assertNotNull(entity.id) 42 | assertEquals(entity.username, "default") 43 | assertNull(entity.age) 44 | } 45 | 46 | @Test 47 | fun bulkInsert() { 48 | database.bulkInsert(Users) { 49 | item { 50 | set(it.id, 10) 51 | set(it.username, it.username.defaultValue()) 52 | set(it.age, 10) 53 | } 54 | item { 55 | set(it.id, 11) 56 | set(it.username, it.username.defaultValue()) 57 | set(it.age, 11) 58 | } 59 | } 60 | val defaultValues = database.users.filter { it.id inList (10..11).toList() }.toList() 61 | assertEquals(defaultValues.size, 2) 62 | for (defaultValue in defaultValues) { 63 | assertEquals(defaultValue.username, "default") 64 | } 65 | 66 | database.bulkInsertOrUpdate(Users) { 67 | item { 68 | set(it.id, 10) 69 | set(it.age, 10) 70 | } 71 | onConflict(it.id) { 72 | set(it.age, it.age.defaultValue()) 73 | } 74 | } 75 | val user = database.users.first { it.id eq 10 } 76 | assertNull(user.age) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/FunctionsTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.postgresql 2 | 3 | import org.junit.Test 4 | import org.ktorm.dsl.* 5 | import org.ktorm.entity.tupleOf 6 | import org.ktorm.schema.Table 7 | import org.ktorm.schema.int 8 | import kotlin.test.assertEquals 9 | 10 | class FunctionsTest : BasePostgreSqlTest() { 11 | 12 | object Arrays : Table("t_array") { 13 | val id = int("id").primaryKey() 14 | val shorts = shortArray("shorts") 15 | val ints = intArray("ints") 16 | val longs = longArray("longs") 17 | val floats = floatArray("floats") 18 | val doubles = doubleArray("doubles") 19 | val booleans = booleanArray("booleans") 20 | val texts = textArray("texts") 21 | } 22 | 23 | @Test 24 | fun testArrayPosition() { 25 | database.insert(Arrays) { 26 | set(it.shorts, shortArrayOf(1, 2, 3, 4)) 27 | set(it.ints, intArrayOf(1, 2, 3, 4)) 28 | set(it.longs, longArrayOf(1, 2, 3, 4)) 29 | set(it.floats, floatArrayOf(1.0F, 2.0F, 3.0F, 4.0F)) 30 | set(it.doubles, doubleArrayOf(1.0, 2.0, 3.0, 4.0)) 31 | set(it.booleans, booleanArrayOf(false, true)) 32 | set(it.texts, arrayOf("1", "2", "3", "4")) 33 | } 34 | 35 | val results = database 36 | .from(Arrays) 37 | .select( 38 | arrayPosition(Arrays.shorts, 2), 39 | arrayPosition(Arrays.ints, 2, 1), 40 | arrayPosition(Arrays.longs, 2), 41 | arrayPosition(Arrays.booleans, true), 42 | arrayPosition(Arrays.texts, "2") 43 | ) 44 | .where(Arrays.id eq 1) 45 | .map { row -> 46 | tupleOf(row.getInt(1), row.getInt(2), row.getInt(3), row.getInt(4), row.getInt(5)) 47 | } 48 | 49 | println(results) 50 | assert(results.size == 1) 51 | 52 | // text[] is one-based, others are zero-based. See https://stackoverflow.com/questions/69649737/postgres-array-positionarray-element-sometimes-0-indexed 53 | assert(results[0] == tupleOf(1, 1, 1, 1, 2)) 54 | } 55 | 56 | @Test 57 | fun testArrayPositionTextArray() { 58 | val namesSorted = database 59 | .from(Employees) 60 | .select() 61 | .orderBy(arrayPosition(arrayOf("tom", "vince", "marry"), Employees.name).asc()) 62 | .map { row -> 63 | row[Employees.name] 64 | } 65 | 66 | assertEquals(listOf("tom", "vince", "marry", "penny"), namesSorted) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/test/kotlin/org/ktorm/support/postgresql/QueryTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.postgresql 2 | 3 | import org.junit.Test 4 | import org.ktorm.database.use 5 | import org.ktorm.dsl.* 6 | import org.ktorm.schema.TextSqlType 7 | import java.sql.Clob 8 | import kotlin.test.assertEquals 9 | 10 | class QueryTest : BasePostgreSqlTest() { 11 | 12 | @Test 13 | fun testCast() { 14 | val salaries = database 15 | .from(Employees) 16 | .select(Employees.salary.cast(TextSqlType)) 17 | .where { Employees.salary eq 200 } 18 | .map { row -> 19 | when (val value = row.getObject(1)) { 20 | is Clob -> value.characterStream.use { it.readText() } 21 | else -> value 22 | } 23 | } 24 | 25 | assertEquals(listOf("200"), salaries) 26 | } 27 | 28 | @Test 29 | fun testUnion() { 30 | val query = database 31 | .from(Employees) 32 | .select(Employees.id) 33 | .union( 34 | database.from(Departments).select(Departments.id) 35 | ) 36 | .union( 37 | database.from(Departments).select(Departments.id) 38 | ) 39 | .orderBy(Employees.id.desc()) 40 | .limit(0, 4) 41 | 42 | println(query.sql) 43 | 44 | val results = query.joinToString { row -> row.getString(1).orEmpty() } 45 | assertEquals("4, 3, 2, 1", results) 46 | } 47 | 48 | @Test 49 | fun testUnionAll() { 50 | val query = database 51 | .from(Employees) 52 | .select(Employees.id) 53 | .unionAll( 54 | database.from(Departments).select(Departments.id) 55 | ) 56 | .unionAll( 57 | database.from(Departments).select(Departments.id) 58 | ) 59 | .orderBy(Employees.id.desc()) 60 | 61 | println(query.sql) 62 | 63 | val results = query.joinToString { row -> row.getString(1).orEmpty() } 64 | assertEquals("4, 3, 2, 2, 2, 1, 1, 1", results) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/test/resources/drop-postgresql-data.sql: -------------------------------------------------------------------------------- 1 | drop table if exists t_department; 2 | drop table if exists t_employee; 3 | drop table if exists t_multi_generated_key; 4 | drop table if exists t_metadata; 5 | drop table if exists t_array; 6 | drop table if exists t_enum; 7 | drop type if exists mood; 8 | drop table if exists t_json; 9 | drop table t_earthdistance; 10 | drop table if exists t_user; 11 | -------------------------------------------------------------------------------- /ktorm-support-postgresql/src/test/resources/init-postgresql-data.sql: -------------------------------------------------------------------------------- 1 | create extension if not exists hstore; 2 | create extension if not exists earthdistance cascade; 3 | create extension if not exists "uuid-ossp"; 4 | 5 | create table t_department( 6 | id serial primary key, 7 | name varchar(128) not null, 8 | location varchar(128) not null, 9 | "mixedCase" varchar(123) 10 | ); 11 | 12 | create table t_employee( 13 | id serial primary key, 14 | name varchar(128) not null, 15 | job varchar(128) not null, 16 | manager_id int null, 17 | hire_date date not null, 18 | salary bigint not null, 19 | department_id int not null 20 | ); 21 | 22 | create table t_multi_generated_key( 23 | id serial primary key, 24 | k varchar(128) not null default uuid_generate_v4(), 25 | v varchar(128) 26 | ); 27 | 28 | create table t_metadata( 29 | id serial primary key, 30 | attrs hstore 31 | ); 32 | 33 | create table t_array( 34 | id serial primary key, 35 | shorts smallint[], 36 | ints integer[], 37 | longs bigint[], 38 | floats real[], 39 | doubles float8[], 40 | booleans boolean[], 41 | texts text[] 42 | ); 43 | 44 | create type mood as enum ('SAD', 'HAPPY'); 45 | create table t_enum( 46 | id serial primary key, 47 | current_mood mood 48 | ); 49 | 50 | create table t_json (obj json, arr json); 51 | 52 | create table t_earthdistance(earth_field earth, cube_field cube); 53 | 54 | create table t_user( 55 | id serial primary key, 56 | username varchar default 'default', 57 | age int 58 | ); 59 | 60 | insert into t_department(name, location, "mixedCase") values ('tech', 'Guangzhou', 'one'); 61 | insert into t_department(name, location, "mixedCase") values ('finance', 'Beijing', 'two'); 62 | 63 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 64 | values ('vince', 'engineer', null, '2018-01-01', 100, 1); 65 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 66 | values ('marry', 'trainee', 1, '2019-01-01', 50, 1); 67 | 68 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 69 | values ('tom', 'director', null, '2018-01-01', 200, 2); 70 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 71 | values ('penny', 'assistant', 3, '2019-01-01', 100, 2); 72 | 73 | insert into t_metadata(attrs) 74 | values ('a=>1, b=>2, c=>NULL'::hstore); 75 | 76 | insert into t_enum(current_mood) 77 | values ('HAPPY') 78 | -------------------------------------------------------------------------------- /ktorm-support-sqlite/ktorm-support-sqlite.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.modularity") 5 | id("ktorm.publish") 6 | id("ktorm.source-header-check") 7 | } 8 | 9 | dependencies { 10 | api(project(":ktorm-core")) 11 | testImplementation(project(":ktorm-core", configuration = "testOutput")) 12 | testImplementation("org.xerial:sqlite-jdbc:3.39.2.0") 13 | } 14 | -------------------------------------------------------------------------------- /ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/SQLiteDialect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.support.sqlite 18 | 19 | import org.ktorm.database.* 20 | import org.ktorm.expression.* 21 | 22 | /** 23 | * [SqlDialect] implementation for SQLite database. 24 | */ 25 | public open class SQLiteDialect : SqlDialect { 26 | 27 | override fun createExpressionVisitor(interceptor: SqlExpressionVisitorInterceptor): SqlExpressionVisitor { 28 | return SQLiteExpressionVisitor::class.newVisitorInstance(interceptor) 29 | } 30 | 31 | override fun createSqlFormatter(database: Database, beautifySql: Boolean, indentSize: Int): SqlFormatter { 32 | return SQLiteFormatter(database, beautifySql, indentSize) 33 | } 34 | 35 | override fun executeUpdateAndRetrieveKeys( 36 | database: Database, 37 | sql: String, 38 | args: List> 39 | ): Pair { 40 | database.useConnection { conn -> 41 | val effects = conn.prepareStatement(sql).use { statement -> 42 | statement.setArguments(args) 43 | statement.executeUpdate() 44 | } 45 | 46 | val retrieveKeySql = "SELECT LAST_INSERT_ROWID()" 47 | if (database.logger.isDebugEnabled()) { 48 | database.logger.debug("Retrieving generated keys by SQL: $retrieveKeySql") 49 | } 50 | 51 | val rowSet = conn.prepareStatement(retrieveKeySql).use { statement -> 52 | statement.executeQuery().use { rs -> CachedRowSet(rs) } 53 | } 54 | 55 | return Pair(effects, rowSet) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ktorm-support-sqlite/src/main/moditect/module-info.java: -------------------------------------------------------------------------------- 1 | module ktorm.support.sqlite { 2 | requires ktorm.core; 3 | exports org.ktorm.support.sqlite; 4 | provides org.ktorm.database.SqlDialect with org.ktorm.support.sqlite.SQLiteDialect; 5 | } 6 | -------------------------------------------------------------------------------- /ktorm-support-sqlite/src/main/resources/META-INF/services/org.ktorm.database.SqlDialect: -------------------------------------------------------------------------------- 1 | org.ktorm.support.sqlite.SQLiteDialect -------------------------------------------------------------------------------- /ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/BaseSQLiteTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.sqlite 2 | 3 | import org.ktorm.BaseTest 4 | import org.ktorm.database.Database 5 | import java.sql.Connection 6 | import java.sql.DriverManager 7 | 8 | abstract class BaseSQLiteTest : BaseTest() { 9 | lateinit var connection: Connection 10 | 11 | override fun init() { 12 | connection = DriverManager.getConnection("jdbc:sqlite::memory:") 13 | 14 | database = Database.connect { 15 | object : Connection by connection { 16 | override fun close() { 17 | // do nothing... 18 | } 19 | } 20 | } 21 | 22 | execSqlScript("init-sqlite-data.sql") 23 | } 24 | 25 | override fun destroy() { 26 | execSqlScript("drop-sqlite-data.sql") 27 | connection.close() 28 | } 29 | } -------------------------------------------------------------------------------- /ktorm-support-sqlite/src/test/kotlin/org/ktorm/support/sqlite/QueryTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.sqlite 2 | 3 | import org.junit.Test 4 | import org.ktorm.database.use 5 | import org.ktorm.dsl.* 6 | import org.ktorm.schema.TextSqlType 7 | import java.sql.Clob 8 | import kotlin.test.assertContentEquals 9 | import kotlin.test.assertEquals 10 | 11 | class QueryTest : BaseSQLiteTest() { 12 | 13 | @Test 14 | fun testCast() { 15 | val salaries = database 16 | .from(Employees) 17 | .select(Employees.salary.cast(TextSqlType)) 18 | .where { Employees.salary eq 200 } 19 | .map { row -> 20 | when (val value = row.getObject(1)) { 21 | is Clob -> value.characterStream.use { it.readText() } 22 | else -> value 23 | } 24 | } 25 | 26 | assertContentEquals(listOf("200"), salaries) 27 | } 28 | 29 | @Test 30 | fun testUnion() { 31 | val query = database 32 | .from(Employees) 33 | .select(Employees.id) 34 | .union( 35 | database.from(Departments).select(Departments.id) 36 | ) 37 | .union( 38 | database.from(Departments).select(Departments.id) 39 | ) 40 | .orderBy(Employees.id.desc()) 41 | .limit(0, 4) 42 | 43 | println(query.sql) 44 | 45 | val results = query.joinToString { row -> row.getString(1).orEmpty() } 46 | assertEquals("4, 3, 2, 1", results) 47 | } 48 | 49 | @Test 50 | fun testUnionAll() { 51 | val query = database 52 | .from(Employees) 53 | .select(Employees.id) 54 | .unionAll( 55 | database.from(Departments).select(Departments.id) 56 | ) 57 | .unionAll( 58 | database.from(Departments).select(Departments.id) 59 | ) 60 | .orderBy(Employees.id.desc()) 61 | 62 | println(query.sql) 63 | 64 | val results = query.joinToString { row -> row.getString(1).orEmpty() } 65 | assertEquals("4, 3, 2, 2, 2, 1, 1, 1", results) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ktorm-support-sqlite/src/test/resources/drop-sqlite-data.sql: -------------------------------------------------------------------------------- 1 | drop table if exists "t_department"; 2 | drop table if exists "t_employee"; 3 | drop table if exists "t_employee0"; 4 | drop table if exists "t_multi_generated_key"; -------------------------------------------------------------------------------- /ktorm-support-sqlite/src/test/resources/init-sqlite-data.sql: -------------------------------------------------------------------------------- 1 | create table t_department( 2 | id integer primary key autoincrement, 3 | name text not null, 4 | location text not null, 5 | mixedCase text 6 | ); 7 | 8 | create table t_employee( 9 | id integer primary key autoincrement, 10 | name text not null, 11 | job text not null, 12 | manager_id integer null, 13 | hire_date integer not null, 14 | salary integer not null, 15 | department_id integer not null 16 | ); 17 | 18 | create table t_multi_generated_key( 19 | id integer primary key autoincrement, 20 | k text not null default ((strftime('%s','now'))), 21 | v text not null 22 | ); 23 | 24 | insert into t_department(name, location) values ('tech', 'Guangzhou'); 25 | insert into t_department(name, location) values ('finance', 'Beijing'); 26 | 27 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 28 | values ('vince', 'engineer', null, 1514736000000, 100, 1); 29 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 30 | values ('marry', 'trainee', 1, 1546272000000, 50, 1); 31 | 32 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 33 | values ('tom', 'director', null, 1514736000000, 200, 2); 34 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 35 | values ('penny', 'assistant', 3, 1546272000000, 100, 2); 36 | -------------------------------------------------------------------------------- /ktorm-support-sqlserver/ktorm-support-sqlserver.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("ktorm.base") 4 | id("ktorm.modularity") 5 | id("ktorm.publish") 6 | id("ktorm.source-header-check") 7 | } 8 | 9 | dependencies { 10 | api(project(":ktorm-core")) 11 | testImplementation(project(":ktorm-core", configuration = "testOutput")) 12 | testImplementation("org.testcontainers:mssqlserver:1.19.7") 13 | testImplementation("com.microsoft.sqlserver:mssql-jdbc:7.2.2.jre8") 14 | } 15 | -------------------------------------------------------------------------------- /ktorm-support-sqlserver/src/main/kotlin/org/ktorm/support/sqlserver/SqlServerDialect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | package org.ktorm.support.sqlserver 18 | 19 | import org.ktorm.database.Database 20 | import org.ktorm.database.SqlDialect 21 | import org.ktorm.expression.* 22 | 23 | /** 24 | * [SqlDialect] implementation for Microsoft SqlServer database. 25 | */ 26 | public open class SqlServerDialect : SqlDialect { 27 | 28 | override fun createSqlFormatter(database: Database, beautifySql: Boolean, indentSize: Int): SqlFormatter { 29 | return SqlServerFormatter(database, beautifySql, indentSize) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ktorm-support-sqlserver/src/main/kotlin/org/ktorm/support/sqlserver/SqlTypes.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2024 the original author or authors. 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 | 17 | @file:Suppress("MatchingDeclarationName") 18 | 19 | package org.ktorm.support.sqlserver 20 | 21 | import org.ktorm.schema.BaseTable 22 | import org.ktorm.schema.Column 23 | import org.ktorm.schema.SqlType 24 | import java.lang.reflect.InvocationTargetException 25 | import java.sql.PreparedStatement 26 | import java.sql.ResultSet 27 | import java.sql.Timestamp 28 | import java.time.OffsetDateTime 29 | 30 | /** 31 | * Type code constant copied from microsoft.sql.Types.DATETIMEOFFSET. 32 | */ 33 | private const val TYPE_CODE_DATETIMEOFFSET = -155 34 | 35 | /** 36 | * Define a column typed of [DateTimeOffsetSqlType]. 37 | */ 38 | public fun BaseTable<*>.datetimeoffset(name: String): Column { 39 | return registerColumn(name, DateTimeOffsetSqlType) 40 | } 41 | 42 | /** 43 | * [SqlType] implementation represents SQL Server `datetimeoffset` SQL type. 44 | */ 45 | public object DateTimeOffsetSqlType : SqlType(TYPE_CODE_DATETIMEOFFSET, "datetimeoffset") { 46 | // Access sqlserver API by reflection, because it is not a JDK 9 module, 47 | // we are not able to require it in module-info.java. 48 | private val cls = Class.forName("microsoft.sql.DateTimeOffset") 49 | private val valueOfMethod = cls.getMethod("valueOf", Timestamp::class.java, Int::class.javaPrimitiveType) 50 | private val getOffsetDateTimeMethod = cls.getMethod("getOffsetDateTime") 51 | 52 | override fun doSetParameter(ps: PreparedStatement, index: Int, parameter: OffsetDateTime) { 53 | @Suppress("SwallowedException") 54 | try { 55 | val ts = Timestamp.from(parameter.toInstant()) 56 | val offset = parameter.offset.totalSeconds / 60 57 | val value = valueOfMethod.invoke(null, ts, offset) 58 | ps.setObject(index, value, typeCode) 59 | } catch (e: InvocationTargetException) { 60 | throw e.targetException 61 | } 62 | } 63 | 64 | override fun doGetResult(rs: ResultSet, index: Int): OffsetDateTime? { 65 | val obj = cls.cast(rs.getObject(index)) 66 | if (obj == null) { 67 | return null 68 | } else { 69 | @Suppress("SwallowedException") 70 | try { 71 | return getOffsetDateTimeMethod.invoke(obj) as OffsetDateTime 72 | } catch (e: InvocationTargetException) { 73 | throw e.targetException 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ktorm-support-sqlserver/src/main/moditect/module-info.java: -------------------------------------------------------------------------------- 1 | module ktorm.support.sqlserver { 2 | requires ktorm.core; 3 | exports org.ktorm.support.sqlserver; 4 | provides org.ktorm.database.SqlDialect with org.ktorm.support.sqlserver.SqlServerDialect; 5 | } 6 | -------------------------------------------------------------------------------- /ktorm-support-sqlserver/src/main/resources/META-INF/services/org.ktorm.database.SqlDialect: -------------------------------------------------------------------------------- 1 | org.ktorm.support.sqlserver.SqlServerDialect -------------------------------------------------------------------------------- /ktorm-support-sqlserver/src/test/kotlin/org/ktorm/support/sqlserver/BaseSqlServerTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.sqlserver 2 | 3 | import org.ktorm.BaseTest 4 | import org.ktorm.database.Database 5 | import org.testcontainers.containers.MSSQLServerContainer 6 | import kotlin.concurrent.thread 7 | 8 | abstract class BaseSqlServerTest : BaseTest() { 9 | 10 | override fun init() { 11 | database = Database.connect(jdbcUrl, driverClassName, username, password) 12 | execSqlScript("init-sqlserver-data.sql") 13 | } 14 | 15 | override fun destroy() { 16 | execSqlScript("drop-sqlserver-data.sql") 17 | } 18 | 19 | companion object : MSSQLServerContainer("mcr.microsoft.com/mssql/server:2017-CU12") { 20 | init { 21 | // Start the container when it's first used. 22 | start() 23 | // Stop the container when the process exits. 24 | Runtime.getRuntime().addShutdownHook(thread(start = false) { stop() }) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /ktorm-support-sqlserver/src/test/kotlin/org/ktorm/support/sqlserver/QueryTest.kt: -------------------------------------------------------------------------------- 1 | package org.ktorm.support.sqlserver 2 | 3 | import org.junit.Test 4 | import org.ktorm.dsl.* 5 | import kotlin.test.assertContentEquals 6 | import kotlin.test.assertEquals 7 | 8 | class QueryTest : BaseSqlServerTest() { 9 | 10 | @Test 11 | fun testCast() { 12 | val salaries = database 13 | .from(Employees) 14 | .select(Employees.salary.toFloat()) 15 | .where { Employees.salary eq 200 } 16 | .map { row -> row.getObject(1) } 17 | 18 | assertContentEquals(listOf(200.0), salaries) 19 | } 20 | 21 | @Test 22 | fun testUnion() { 23 | val query = database 24 | .from(Employees) 25 | .select(Employees.id) 26 | .union( 27 | database.from(Departments).select(Departments.id) 28 | ) 29 | .union( 30 | database.from(Departments).select(Departments.id) 31 | ) 32 | .orderBy(Employees.id.desc()) 33 | .limit(0, 4) 34 | 35 | println(query.sql) 36 | 37 | val results = query.joinToString { row -> row.getString(1).orEmpty() } 38 | assertEquals("4, 3, 2, 1", results) 39 | } 40 | 41 | @Test 42 | fun testUnionAll() { 43 | val query = database 44 | .from(Employees) 45 | .select(Employees.id) 46 | .unionAll( 47 | database.from(Departments).select(Departments.id) 48 | ) 49 | .unionAll( 50 | database.from(Departments).select(Departments.id) 51 | ) 52 | .orderBy(Employees.id.desc()) 53 | 54 | println(query.sql) 55 | 56 | val results = query.joinToString { row -> row.getString(1).orEmpty() } 57 | assertEquals("4, 3, 2, 2, 2, 1, 1, 1", results) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ktorm-support-sqlserver/src/test/resources/container-license-acceptance.txt: -------------------------------------------------------------------------------- 1 | mcr.microsoft.com/mssql/server:2017-CU12 -------------------------------------------------------------------------------- /ktorm-support-sqlserver/src/test/resources/drop-sqlserver-data.sql: -------------------------------------------------------------------------------- 1 | drop table t_department; 2 | drop table t_employee; 3 | drop table foo; -------------------------------------------------------------------------------- /ktorm-support-sqlserver/src/test/resources/init-sqlserver-data.sql: -------------------------------------------------------------------------------- 1 | create table t_department( 2 | id int not null identity(1, 1), 3 | name varchar(128) not null, 4 | location varchar(128) not null, 5 | mixedCase varchar(128) 6 | ); 7 | 8 | create table t_employee( 9 | id int not null identity(1, 1), 10 | name varchar(128) not null, 11 | job varchar(128) not null, 12 | manager_id int null, 13 | hire_date date not null, 14 | salary bigint not null, 15 | department_id int not null 16 | ); 17 | 18 | insert into t_department(name, location) values ('tech', 'Guangzhou'); 19 | insert into t_department(name, location) values ('finance', 'Beijing'); 20 | 21 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 22 | values ('vince', 'engineer', null, '2018-01-01', 100, 1); 23 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 24 | values ('marry', 'trainee', 1, '2019-01-01', 50, 1); 25 | 26 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 27 | values ('tom', 'director', null, '2018-01-01', 200, 2); 28 | insert into t_employee(name, job, manager_id, hire_date, salary, department_id) 29 | values ('penny', 'assistant', 3, '2019-01-01', 100, 2); 30 | 31 | create table foo (bar datetimeoffset(7) not null, bar1 datetime not null); 32 | insert into foo (bar, bar1) values ('2012-10-25 12:32:10 +01:00', '2012-10-25 19:32:10'); 33 | -------------------------------------------------------------------------------- /ktorm.version: -------------------------------------------------------------------------------- 1 | 4.1.1 2 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("com.gradle.enterprise") version "3.14.1" 4 | } 5 | 6 | include("ktorm-core") 7 | include("ktorm-global") 8 | include("ktorm-jackson") 9 | include("ktorm-ksp-annotations") 10 | include("ktorm-ksp-compiler") 11 | include("ktorm-ksp-compiler-maven-plugin") 12 | include("ktorm-ksp-spi") 13 | include("ktorm-support-mysql") 14 | include("ktorm-support-oracle") 15 | include("ktorm-support-postgresql") 16 | include("ktorm-support-sqlite") 17 | include("ktorm-support-sqlserver") 18 | 19 | rootProject.name = "ktorm" 20 | rootProject.children.forEach { project -> 21 | project.buildFileName = "${project.name}.gradle.kts" 22 | } 23 | 24 | gradleEnterprise { 25 | if (System.getenv("CI") == "true") { 26 | buildScan { 27 | publishAlways() 28 | termsOfServiceUrl = "https://gradle.com/terms-of-service" 29 | termsOfServiceAgree = "yes" 30 | } 31 | } 32 | } 33 | --------------------------------------------------------------------------------