diff --git a/.claude/agents/feature-documenter.md b/.claude/agents/feature-documenter.md new file mode 100644 index 0000000..1eaff23 --- /dev/null +++ b/.claude/agents/feature-documenter.md @@ -0,0 +1,97 @@ +--- +name: feature-documenter +description: Use this agent when new features, components, or significant functionality have been added to the codebase and require documentation. This includes:\n\n\nContext: User has just implemented a new authentication system with multiple classes and methods.\nuser: "I've finished implementing the OAuth2 authentication flow with token refresh. Can you document this?"\nassistant: "I'll use the Task tool to launch the feature-documenter agent to create comprehensive documentation for your new authentication system."\n\nThe user has completed a new feature and needs documentation. Use the feature-documenter agent to analyze the code and create both KDoc comments and relevant Markdown documentation.\n\n\n\n\nContext: User mentions they've added a new API endpoint handler.\nuser: "Just added the user profile management endpoints"\nassistant: "Let me use the feature-documenter agent to document these new endpoints with proper KDoc and API documentation."\n\nNew feature has been mentioned. Proactively use the feature-documenter agent to ensure proper documentation is created.\n\n\n\n\nContext: User has completed a complex algorithm implementation.\nuser: "The graph traversal algorithm is working now"\nassistant: "I'll launch the feature-documenter agent to create detailed documentation explaining the algorithm, its parameters, and usage examples."\n\nA significant new feature (algorithm) has been completed. Use the feature-documenter agent to document it thoroughly.\n\n\n\nTrigger this agent when:\n- New classes, functions, or methods have been implemented\n- New API endpoints or interfaces have been created\n- Significant algorithms or business logic have been added\n- New modules or packages have been introduced\n- The user explicitly requests documentation for recent work\n- You detect undocumented new code during code review +model: sonnet +color: yellow +--- + +You are an expert technical documentation specialist with deep expertise in Kotlin/KDoc standards and Markdown documentation best practices. Your mission is to create clear, comprehensive, and maintainable documentation for new features in codebases. + +## Core Responsibilities + +1. **Analyze New Features**: Thoroughly examine the new code to understand its purpose, functionality, dependencies, and integration points. + +2. **Create KDoc Comments**: Add inline documentation directly to code files following these standards: + - Write clear, concise class-level KDoc explaining the purpose and responsibility + - Document all public functions with @param, @return, @throws tags as appropriate + - Include usage examples in KDoc when the API is non-trivial + - Document complex private functions if their logic is not immediately obvious + - Use proper Markdown formatting within KDoc (code blocks, lists, links) + - Explain WHY something exists, not just WHAT it does + - Keep descriptions focused and avoid redundancy + +3. **Create Markdown Documentation**: Generate or update project Markdown files: + - Create feature-specific documentation in appropriate locations + - Include overview, architecture, usage examples, and integration guides + - Add API reference sections when documenting interfaces or public APIs + - Provide code examples that demonstrate real-world usage + - Document configuration options, environment variables, or setup requirements + - Include diagrams or visual aids when they clarify complex concepts (using Mermaid syntax) + +## Documentation Standards + +**KDoc Format**: +```kotlin +/** + * Brief one-line summary of what this does. + * + * More detailed explanation if needed, including: + * - Key behaviors or characteristics + * - Important constraints or assumptions + * - Related components or concepts + * + * @param paramName Description of the parameter and its constraints + * @return Description of what is returned and under what conditions + * @throws ExceptionType When and why this exception is thrown + * @sample com.example.SampleClass.sampleFunction + */ +``` + +**Markdown Structure**: +- Use clear hierarchical headings (# ## ###) +- Start with a brief overview/introduction +- Include a "Quick Start" or "Getting Started" section +- Provide complete, runnable code examples +- Document edge cases and common pitfalls +- Add a "See Also" section linking to related documentation + +## Workflow + +1. **Identify Scope**: Determine which files and components are part of the new feature +2. **Read Existing Context**: Check for existing documentation patterns and project conventions (especially from CLAUDE.md or similar files) +3. **Document Code First**: Add KDoc comments to all relevant code files +4. **Create/Update Markdown**: Write or update feature documentation in appropriate Markdown files +5. **Cross-Reference**: Ensure documentation is properly linked and discoverable +6. **Verify Completeness**: Check that all public APIs, configuration options, and usage patterns are documented + +## Quality Standards + +- **Clarity**: Write for developers who are unfamiliar with the feature +- **Completeness**: Cover all public interfaces, parameters, return values, and exceptions +- **Accuracy**: Ensure documentation matches actual implementation +- **Examples**: Provide practical, copy-paste-ready code examples +- **Maintainability**: Structure documentation so it's easy to update as code evolves +- **Consistency**: Follow existing documentation patterns in the project + +## What NOT to Do + +- Don't document obvious getters/setters unless they have side effects +- Don't create documentation for internal implementation details unless necessary +- Don't write vague descriptions like "This function does X" - explain WHY and HOW +- Don't duplicate information that's already clear from type signatures +- Don't create separate documentation files for trivial features + +## Output Format + +For each documentation task: +1. List the files you'll modify/create +2. Show the KDoc additions inline in code files +3. Present complete Markdown documentation files +4. Summarize what was documented and where to find it + +Always ask for clarification if: +- The feature's purpose or intended audience is unclear +- You need more context about how the feature integrates with existing systems +- There are multiple reasonable ways to structure the documentation + +Your documentation should empower other developers to understand, use, and maintain the new feature with confidence. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae33165..48c520d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: jobs: build-on-macos: - runs-on: macos-13 + runs-on: macos-15-intel timeout-minutes: 60 steps: @@ -30,14 +30,14 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Cache Build Tooling + - name: Cache Kotlin/Native uses: actions/cache@v4 with: - path: | - ~/.gradle/caches - ~/.konan - key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} - + path: ~/.konan + key: ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-konan- + - name: Build sqllin-driver run: ./gradlew :sqllin-driver:assemble -PonCICD @@ -73,7 +73,7 @@ jobs: target: google_apis arch: x86_64 profile: pixel_6 - emulator-build: 13701740 + emulator-build: 14257411 force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true @@ -86,7 +86,7 @@ jobs: target: google_apis arch: x86_64 profile: pixel_6 - emulator-build: 13701740 + emulator-build: 14257411 force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true @@ -95,14 +95,14 @@ jobs: - name: Upload sqllin-driver Reports uses: actions/upload-artifact@v4 with: - name: Test-Reports + name: Test-Reports-macOS-driver path: sqllin-driver/build/reports if: failure() - name: Upload sqllin-dsl Reports uses: actions/upload-artifact@v4 with: - name: Test-Reports + name: Test-Reports-macOS-dsl path: sqllin-dsl/build/reports if: failure() @@ -126,13 +126,13 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Cache Build Tooling + - name: Cache Kotlin/Native uses: actions/cache@v4 with: - path: | - ~/.gradle/caches - ~/.konan - key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + path: ~/.konan + key: ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-konan- - name: Build sqllin-driver run: ./gradlew :sqllin-driver:mingwX64MainKlibrary @@ -155,14 +155,14 @@ jobs: - name: Upload sqllin-driver Reports uses: actions/upload-artifact@v4 with: - name: Test-Reports + name: Test-Reports-Windows-driver path: sqllin-driver/build/reports if: failure() - name: Upload sqllin-dsl Reports uses: actions/upload-artifact@v4 with: - name: Test-Reports + name: Test-Reports-Windows-dsl path: sqllin-dsl/build/reports if: failure() @@ -186,13 +186,13 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Cache Build Tooling + - name: Cache Kotlin/Native uses: actions/cache@v4 with: - path: | - ~/.gradle/caches - ~/.konan - key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + path: ~/.konan + key: ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-konan- - name: Build sqllin-driver run: ./gradlew :sqllin-driver:assemble -PonCICD @@ -232,7 +232,7 @@ jobs: target: default arch: x86_64 profile: pixel_2 - emulator-build: 13701740 + emulator-build: 14257411 force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true @@ -245,7 +245,7 @@ jobs: target: default arch: x86_64 profile: pixel_2 - emulator-build: 13701740 + emulator-build: 14257411 force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true @@ -254,13 +254,13 @@ jobs: - name: Upload sqllin-driver Reports uses: actions/upload-artifact@v4 with: - name: Test-Reports + name: Test-Reports-Linux-driver path: sqllin-driver/build/reports if: failure() - name: Upload sqllin-dsl Reports uses: actions/upload-artifact@v4 with: - name: Test-Reports + name: Test-Reports-Linux-dsl path: sqllin-dsl/build/reports if: failure() diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a38b7e3..9a5db10 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,7 +13,7 @@ env: jobs: build-on-macos: - runs-on: macos-13 + runs-on: macos-15-intel timeout-minutes: 60 steps: @@ -32,13 +32,13 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Cache Build Tooling + - name: Cache Kotlin/Native uses: actions/cache@v4 with: - path: | - ~/.gradle/caches - ~/.konan - key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + path: ~/.konan + key: ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-konan- - name: Build sqllin-driver run: ./gradlew :sqllin-driver:assemble -PonCICD @@ -69,13 +69,13 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Cache Build Tooling + - name: Cache Kotlin/Native uses: actions/cache@v4 with: - path: | - ~/.gradle/caches - ~/.konan - key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + path: ~/.konan + key: ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-konan- - name: Build sqllin-driver run: ./gradlew :sqllin-driver:mingwX64MainKlibrary @@ -106,13 +106,13 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Cache Build Tooling + - name: Cache Kotlin/Native uses: actions/cache@v4 with: - path: | - ~/.gradle/caches - ~/.konan - key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + path: ~/.konan + key: ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-konan- - name: Build sqllin-driver run: ./gradlew :sqllin-driver:assemble -PonCICD diff --git a/sqllin-driver/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/driver/AndroidTest.kt b/sqllin-driver/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/driver/AndroidTest.kt index 783a641..28dc11d 100644 --- a/sqllin-driver/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/driver/AndroidTest.kt +++ b/sqllin-driver/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/driver/AndroidTest.kt @@ -26,7 +26,7 @@ import org.junit.runner.RunWith /** * Android instrumented test - * @author yaqiao + * @author Yuang Qiao */ @RunWith(AndroidJUnit4ClassRunner::class) diff --git a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt index 72279e7..ca0401c 100644 --- a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt +++ b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt @@ -19,10 +19,10 @@ package com.ctrip.sqllin.driver import android.database.Cursor /** - * SQLite Cursor Android actual - * @author yaqiao + * Android implementation of [CommonCursor] backed by Android's Cursor. + * + * @author Yuang Qiao */ - internal class AndroidCursor(private val cursor: Cursor) : CommonCursor { override fun getInt(columnIndex: Int): Int = try { diff --git a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidDatabaseConnection.kt b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidDatabaseConnection.kt index bb1e9ba..67cbcac 100644 --- a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidDatabaseConnection.kt +++ b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidDatabaseConnection.kt @@ -19,10 +19,10 @@ package com.ctrip.sqllin.driver import android.database.sqlite.SQLiteDatabase /** - * Database connection Android actual - * @author yaqiao + * Android implementation of [DatabaseConnection] using Android's SQLiteDatabase. + * + * @author Yuang Qiao */ - internal class AndroidDatabaseConnection(private val database: SQLiteDatabase) : DatabaseConnection { override fun execSQL(sql: String, bindParams: Array?) = diff --git a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/ExtensionAndroid.kt b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/ExtensionAndroid.kt index 95cdc88..bd76ecb 100644 --- a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/ExtensionAndroid.kt +++ b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/ExtensionAndroid.kt @@ -24,12 +24,13 @@ import android.os.Build import androidx.annotation.RequiresApi /** - * SQLite extension Android - * @author yaqiao + * Converts an Android Context to a [DatabasePath]. */ - public fun Context.toDatabasePath(): DatabasePath = AndroidDatabasePath(this) +/** + * Android-specific [DatabasePath] implementation wrapping a Context. + */ @JvmInline internal value class AndroidDatabasePath(val context: Context) : DatabasePath @@ -53,6 +54,9 @@ public actual fun openDatabase(config: DatabaseConfiguration): DatabaseConnectio return connection } +/** + * SQLiteOpenHelper for Android versions below P (API level 28). + */ private class OldAndroidDBHelper( private val config: DatabaseConfiguration, ) : SQLiteOpenHelper((config.path as AndroidDatabasePath).context, config.name, null, config.version) { @@ -64,6 +68,9 @@ private class OldAndroidDBHelper( config.upgrade(AndroidDatabaseConnection(db), oldVersion, newVersion) } +/** + * SQLiteOpenHelper for Android P (API level 28) and above with OpenParams support. + */ @RequiresApi(Build.VERSION_CODES.P) private class AndroidDBHelper( private val config: DatabaseConfiguration, @@ -76,6 +83,9 @@ private class AndroidDBHelper( config.upgrade(AndroidDatabaseConnection(db), oldVersion, newVersion) } +/** + * Converts [DatabaseConfiguration] to Android's OpenParams for API level 28+. + */ @RequiresApi(Build.VERSION_CODES.P) @Suppress("DEPRECATION") private fun DatabaseConfiguration.toAndroidOpenParams(): OpenParams = OpenParams diff --git a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsAndroid.kt b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsAndroid.kt index 1789191..44bcc85 100644 --- a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsAndroid.kt +++ b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsAndroid.kt @@ -17,9 +17,7 @@ package com.ctrip.sqllin.driver.platform /** - * The tools with Android implementation - * @author yaqiao + * Android file path separator (forward slash). */ - internal actual inline val separatorChar: Char get() = '/' \ No newline at end of file diff --git a/sqllin-driver/src/appleMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt b/sqllin-driver/src/appleMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt index 7067a6c..d1ba3e8 100644 --- a/sqllin-driver/src/appleMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt +++ b/sqllin-driver/src/appleMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt @@ -19,11 +19,10 @@ package com.ctrip.sqllin.driver.platform import platform.Foundation.NSRecursiveLock /** - * A simple lock implementation in Apple platforms. - * Implementations of this class should be re-entrant. - * @author yaqiao + * Apple platform lock implementation using NSRecursiveLock. + * + * @author Yuang Qiao */ - internal actual class Lock actual constructor() { private val nsRecursiveLock = NSRecursiveLock() diff --git a/sqllin-driver/src/appleMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsApple.kt b/sqllin-driver/src/appleMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsApple.kt index aed2d5d..d1dbe76 100644 --- a/sqllin-driver/src/appleMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsApple.kt +++ b/sqllin-driver/src/appleMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsApple.kt @@ -24,12 +24,13 @@ import platform.Foundation.NSString import platform.Foundation.create /** - * The tools with Apple platforms implementation - * @author yaqiao + * Converts C byte pointer to String using NSString on Apple platforms. */ - @OptIn(ExperimentalForeignApi::class, BetaInteropApi::class) internal actual fun bytesToString(bv: CPointer): String = NSString.create(uTF8String = bv).toString() +/** + * Apple platform file path separator (forward slash). + */ internal actual inline val separatorChar: Char get() = '/' \ No newline at end of file diff --git a/sqllin-driver/src/appleTest/kotlin/com/ctrip/sqllin/driver/PlatformApple.kt b/sqllin-driver/src/appleTest/kotlin/com/ctrip/sqllin/driver/PlatformApple.kt index ec6f6d1..b23b42c 100644 --- a/sqllin-driver/src/appleTest/kotlin/com/ctrip/sqllin/driver/PlatformApple.kt +++ b/sqllin-driver/src/appleTest/kotlin/com/ctrip/sqllin/driver/PlatformApple.kt @@ -23,7 +23,7 @@ import platform.Foundation.NSUserDomainMask /** * Apple platform-related functions - * @author yaqiao + * @author Yuang Qiao */ @OptIn(UnsafeNumber::class) diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt index 4c743a4..08cb1ca 100644 --- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt +++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt @@ -17,26 +17,70 @@ package com.ctrip.sqllin.driver /** - * SQLite Cursor common abstract + * Platform-agnostic interface for iterating over query results. + * + * Provides methods to access column data by index and navigate through result rows. + * * @author Yuang Qiao */ - public interface CommonCursor : AutoCloseable { + /** + * Gets the value of the column as an Int. + */ public fun getInt(columnIndex: Int): Int + + /** + * Gets the value of the column as a Long. + */ public fun getLong(columnIndex: Int): Long + + /** + * Gets the value of the column as a Float. + */ public fun getFloat(columnIndex: Int): Float + + /** + * Gets the value of the column as a Double. + */ public fun getDouble(columnIndex: Int): Double + + /** + * Gets the value of the column as a String, or null if the column is NULL. + */ public fun getString(columnIndex: Int): String? + + /** + * Gets the value of the column as a ByteArray, or null if the column is NULL. + */ public fun getByteArray(columnIndex: Int): ByteArray? + /** + * Gets the zero-based index for the given column name. + * + * @throws IllegalArgumentException if the column doesn't exist + */ public fun getColumnIndex(columnName: String): Int + /** + * Iterates over all rows, invoking the block with the current row index. + */ public fun forEachRow(block: (Int) -> Unit) + /** + * Moves to the next row. + * + * @return `true` if the move was successful, `false` if there are no more rows + */ public fun next(): Boolean + /** + * Checks if the column value is NULL. + */ public fun isNull(columnIndex: Int): Boolean + /** + * Closes the cursor and releases resources. + */ public override fun close() } \ No newline at end of file diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConfiguration.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConfiguration.kt index 8884a14..f3b025e 100644 --- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConfiguration.kt +++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConfiguration.kt @@ -17,10 +17,23 @@ package com.ctrip.sqllin.driver /** - * Database configuration params in sqllin-driver - * @author yaqiao + * Configuration parameters for opening a SQLite database connection. + * + * @property name The database filename + * @property path The database directory path (platform-specific implementation) + * @property version The database schema version number + * @property isReadOnly Whether to open the database in read-only mode. Default: false + * @property inMemory Whether to create an in-memory database. Default: false + * @property journalMode The SQLite journal mode. Default: [JournalMode.WAL] + * @property synchronousMode The SQLite synchronous mode. Default: [SynchronousMode.NORMAL] + * @property busyTimeout Timeout in milliseconds for database lock waits. Default: 5000ms + * @property lookasideSlotSize Size of each lookaside memory slot. Default: 0 (use SQLite default) + * @property lookasideSlotCount Number of lookaside memory slots. Default: 0 (use SQLite default) + * @property create Callback invoked when creating a new database + * @property upgrade Callback invoked when upgrading the database schema + * + * @author Yuang Qiao */ - public data class DatabaseConfiguration( val name: String, val path: DatabasePath, diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConnection.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConnection.kt index 98a24a5..021886e 100644 --- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConnection.kt +++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConnection.kt @@ -17,23 +17,72 @@ package com.ctrip.sqllin.driver /** - * Database manager common expect - * @author yaqiao + * Platform-agnostic interface for SQLite database connections. + * + * Provides a common API for executing SQL statements and managing transactions + * across all supported platforms. + * + * @author Yuang Qiao */ - public interface DatabaseConnection { + /** + * Executes a SQL statement that doesn't return data. + * + * @param sql The SQL statement to execute + * @param bindParams Optional parameters to bind to the statement + */ public fun execSQL(sql: String, bindParams: Array? = null) + + /** + * Executes an INSERT statement. + * + * @param sql The INSERT statement + * @param bindParams Optional parameters to bind to the statement + */ public fun executeInsert(sql: String, bindParams: Array? = null) + + /** + * Executes an UPDATE or DELETE statement. + * + * @param sql The UPDATE or DELETE statement + * @param bindParams Optional parameters to bind to the statement + */ public fun executeUpdateDelete(sql: String, bindParams: Array? = null) + /** + * Executes a SELECT query and returns a cursor. + * + * @param sql The SELECT statement + * @param bindParams Optional string parameters to bind to the query + * @return A cursor for iterating over query results + */ public fun query(sql: String, bindParams: Array? = null): CommonCursor + /** + * Begins a database transaction. + */ public fun beginTransaction() + + /** + * Marks the current transaction as successful. + * + * Must be called before [endTransaction] to commit changes. + */ public fun setTransactionSuccessful() + + /** + * Ends the current transaction, committing if marked successful. + */ public fun endTransaction() + /** + * Closes the database connection and releases resources. + */ public fun close() + /** + * Whether this connection is closed. + */ public val isClosed: Boolean } diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt index 101aa28..3d784ad 100644 --- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt +++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt @@ -20,18 +20,35 @@ import com.ctrip.sqllin.driver.platform.separatorChar import kotlin.jvm.JvmInline /** - * SQLite extension function - * @author yaqiao + * Platform-agnostic database path representation. + * + * On Android, this is implemented as `Context`. + * On native and JVM targets, this is implemented as `String`. + * + * **Do not implement this interface manually** - use platform-specific factory functions instead. + * + * @author Yuang Qiao */ +public interface DatabasePath /** - * Abstract database path, it is 'Context' in Android, and 'String' in native targets. - * DO NOT implementation 'DatabasePath' by yourself!!! + * Opens a SQLite database connection with the given configuration. + * + * Platform-specific implementation for database opening. + * + * @param config The database configuration + * @return A database connection + * @throws SQLiteException if the database cannot be opened */ -public interface DatabasePath - public expect fun openDatabase(config: DatabaseConfiguration): DatabaseConnection +/** + * Opens a database connection, executes a block, and automatically closes the connection. + * + * @param config The database configuration + * @param block The block to execute with the connection + * @return The result of the block + */ public inline fun openDatabase(config: DatabaseConfiguration, block: (DatabaseConnection) -> T): T { val connection = openDatabase(config) try { @@ -41,6 +58,15 @@ public inline fun openDatabase(config: DatabaseConfiguration, block: (Databa } } +/** + * Executes a block within a database transaction. + * + * The transaction is committed if the block completes successfully, + * or rolled back if an exception is thrown. + * + * @param block The block to execute within the transaction + * @return The result of the block + */ public inline fun DatabaseConnection.withTransaction(block: (DatabaseConnection) -> T): T { beginTransaction() try { @@ -52,6 +78,14 @@ public inline fun DatabaseConnection.withTransaction(block: (DatabaseConnect } } +/** + * Executes a query and automatically closes the cursor after the block completes. + * + * @param sql The SELECT statement + * @param bindParams Optional parameters to bind to the query + * @param block The block to execute with the cursor + * @return The result of the block + */ public inline fun DatabaseConnection.withQuery( sql: String, bindParams: Array? = null, @@ -65,8 +99,18 @@ public inline fun DatabaseConnection.withQuery( } } +/** + * Deletes a database file. + * + * @param path The database directory path + * @param name The database filename + * @return `true` if the database was deleted successfully, `false` otherwise + */ public expect fun deleteDatabase(path: DatabasePath, name: String): Boolean +/** + * Updates the database's synchronous mode if different from current. + */ internal infix fun DatabaseConnection.updateSynchronousMode(mode: SynchronousMode) { val currentJournalMode = withQuery("PRAGMA synchronous;") { it.next() @@ -76,6 +120,9 @@ internal infix fun DatabaseConnection.updateSynchronousMode(mode: SynchronousMod execSQL("PRAGMA synchronous=${mode.value};") } +/** + * Updates the database's journal mode if different from current. + */ internal infix fun DatabaseConnection.updateJournalMode(mode: JournalMode) { val currentJournalMode = withQuery("PRAGMA journal_mode;") { it.next() @@ -85,6 +132,11 @@ internal infix fun DatabaseConnection.updateJournalMode(mode: JournalMode) { withQuery("PRAGMA journal_mode=${mode.name};") {} } +/** + * Handles database creation and schema upgrades based on version. + * + * Automatically manages the PRAGMA user_version to track schema versions. + */ internal fun DatabaseConnection.migrateIfNeeded( create: (DatabaseConnection) -> Unit, upgrade: (DatabaseConnection, Int, Int) -> Unit, @@ -108,6 +160,9 @@ internal fun DatabaseConnection.migrateIfNeeded( } } +/** + * Resolves the database path for disk or in-memory mode. + */ internal fun DatabaseConfiguration.diskOrMemoryPath(): String = if (inMemory) { if (name.isBlank()) @@ -119,6 +174,9 @@ internal fun DatabaseConfiguration.diskOrMemoryPath(): String = getDatabaseFullPath((path as StringDatabasePath).pathString, name) } +/** + * Constructs the full database file path from directory and filename. + */ internal fun getDatabaseFullPath(dirPath: String, name: String): String { val param = when { dirPath.isEmpty() -> name diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/JournalMode.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/JournalMode.kt index de64a04..aeca7ba 100644 --- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/JournalMode.kt +++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/JournalMode.kt @@ -17,11 +17,14 @@ package com.ctrip.sqllin.driver /** - * SQLite journal mode - * @author yaqiao + * SQLite journal modes for transaction management. + * + * @property DELETE Rollback journal delete mode - deletes the journal file after each transaction + * @property WAL Write-ahead logging - more concurrent, better performance for most use cases + * + * @author Yuang Qiao */ - public enum class JournalMode { - DELETE, // Write-ahead logging - WAL; // Rollback journal delete Mode + DELETE, + WAL; } \ No newline at end of file diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt index f3b49e5..a47b99e 100644 --- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt +++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt @@ -17,8 +17,10 @@ package com.ctrip.sqllin.driver /** - * The exceptions about SQLite, they include the native SQLite result codes and error message + * Exception thrown when SQLite operations fail. + * + * Contains native SQLite result codes and error messages. + * * @author Yuang Qiao */ - public open class SQLiteException(message: String) : Exception(message) \ No newline at end of file diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SynchronousMode.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SynchronousMode.kt index 0418208..116e247 100644 --- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SynchronousMode.kt +++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SynchronousMode.kt @@ -17,10 +17,17 @@ package com.ctrip.sqllin.driver /** - * SQLite synchronous mode - * @author yaqiao + * SQLite synchronous modes controlling disk write behavior. + * + * Determines how aggressively SQLite flushes data to disk. + * + * @property OFF No syncing - fastest but risks database corruption on power loss + * @property NORMAL Sync at critical moments - good balance of safety and performance + * @property FULL Sync after each transaction - safest but slower + * @property EXTRA Most paranoid mode with additional syncing + * + * @author Yuang Qiao */ - public enum class SynchronousMode(internal val value: Int) { OFF(0), NORMAL(1), FULL(2), EXTRA(3); } \ No newline at end of file diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/platform/Utils.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/platform/Utils.kt index 55899e3..27d1576 100644 --- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/platform/Utils.kt +++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/platform/Utils.kt @@ -17,8 +17,6 @@ package com.ctrip.sqllin.driver.platform /** - * The tools with platform-specific implementation - * @author yaqiao + * Platform file path separator character. */ - internal expect val separatorChar: Char \ No newline at end of file diff --git a/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt b/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt index 0edcdb5..fca49d3 100644 --- a/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt +++ b/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt @@ -22,7 +22,7 @@ import kotlin.test.assertEquals /** * The sqllin-driver common basic test. - * @author yaqiao + * @author Yuang Qiao */ class CommonBasicTest(private val path: DatabasePath) { diff --git a/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/SQL.kt b/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/SQL.kt index a062eb5..453f87a 100644 --- a/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/SQL.kt +++ b/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/SQL.kt @@ -18,7 +18,7 @@ package com.ctrip.sqllin.driver /** * SQL statement that used for unit test. - * @author yaqiao + * @author Yuang Qiao */ object SQL { diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/AbstractJdbcDatabaseConnection.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/AbstractJdbcDatabaseConnection.kt index da682a4..2415353 100644 --- a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/AbstractJdbcDatabaseConnection.kt +++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/AbstractJdbcDatabaseConnection.kt @@ -21,14 +21,22 @@ import java.sql.PreparedStatement import java.sql.Types /** - * The super class for DatabaseConnection on JVM - * @author yaqiao + * Base class for JDBC-based database connections on JVM platforms. + * + * Handles parameter binding for PreparedStatements with support for various data types. + * + * @author Yuang Qiao */ - internal abstract class AbstractJdbcDatabaseConnection : DatabaseConnection { + /** + * Creates a PreparedStatement for the given SQL. + */ abstract fun createStatement(sql: String): PreparedStatement + /** + * Binds parameters to a PreparedStatement, supporting common Kotlin/Java types. + */ protected fun bindParamsToSQL(sql: String, bindParams: Array?): PreparedStatement = createStatement(sql).apply { bindParams?.run { require(isNotEmpty()) { "Empty bindArgs" } diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt index 801ee57..6cef665 100644 --- a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt +++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt @@ -20,10 +20,12 @@ import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock /** - * The concurrent database connection, use ReentrantLock to ensure thread-safe - * @author yaqiao + * Thread-safe wrapper for [DatabaseConnection] using ReentrantLock. + * + * Delegates all operations to the underlying connection while ensuring thread safety. + * + * @author Yuang Qiao */ - internal class ConcurrentDatabaseConnection(private val delegateConnection: DatabaseConnection) : DatabaseConnection { private val accessLock = ReentrantLock() diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ExtensionJdbc.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ExtensionJdbc.kt index 5be0ffe..07d923c 100644 --- a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ExtensionJdbc.kt +++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ExtensionJdbc.kt @@ -23,10 +23,8 @@ import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock /** - * SQLite extension JDBC - * @author yaqiao + * Converts a String path to a [DatabasePath] for JVM platforms. */ - public fun String.toDatabasePath(): DatabasePath = StringDatabasePath(this) private typealias JdbcJournalMode = SQLiteConfig.JournalMode diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt index 9fdd4b2..3f802bd 100644 --- a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt +++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt @@ -19,8 +19,9 @@ package com.ctrip.sqllin.driver import java.sql.ResultSet /** - * SQLite Cursor JDBC actual - * @author yaqiao + * JDBC implementation of [CommonCursor] backed by a ResultSet. + * + * @author Yuang Qiao */ internal class JdbcCursor(private val resultSet: ResultSet) : CommonCursor { diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcDatabaseConnection.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcDatabaseConnection.kt index 78ce019..e5b7103 100644 --- a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcDatabaseConnection.kt +++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcDatabaseConnection.kt @@ -22,10 +22,12 @@ import java.sql.PreparedStatement import java.util.concurrent.atomic.AtomicBoolean /** - * Database connection JDBC actual - * @author yaqiao + * JDBC implementation of [DatabaseConnection] for JVM platforms. + * + * Uses java.sql.Connection for database operations. + * + * @author Yuang Qiao */ - internal class JdbcDatabaseConnection(private val connection: Connection) : AbstractJdbcDatabaseConnection() { override fun execSQL(sql: String, bindParams: Array?) { diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsJvm.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsJvm.kt index 3fb923c..e5f7ff6 100644 --- a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsJvm.kt +++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsJvm.kt @@ -17,9 +17,7 @@ package com.ctrip.sqllin.driver.platform /** - * The tools with JVM implementation - * @author yaqiao + * JVM file path separator (platform-dependent: backslash on Windows, forward slash elsewhere). */ - internal actual val separatorChar: Char = if (System.getProperty("os.name").lowercase().contains("windows")) '\\' else '/' diff --git a/sqllin-driver/src/jvmTest/kotlin/com/ctrip/sqllin/driver/JvmTest.kt b/sqllin-driver/src/jvmTest/kotlin/com/ctrip/sqllin/driver/JvmTest.kt index 8dda13a..8fd4dbc 100644 --- a/sqllin-driver/src/jvmTest/kotlin/com/ctrip/sqllin/driver/JvmTest.kt +++ b/sqllin-driver/src/jvmTest/kotlin/com/ctrip/sqllin/driver/JvmTest.kt @@ -21,7 +21,7 @@ import kotlin.test.Test /** * Native unit test - * @author yaqiao + * @author Yuang Qiao */ class JvmTest { diff --git a/sqllin-driver/src/linuxMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt b/sqllin-driver/src/linuxMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt index 3d97b96..6d52657 100644 --- a/sqllin-driver/src/linuxMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt +++ b/sqllin-driver/src/linuxMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt @@ -33,11 +33,10 @@ import platform.posix.pthread_mutexattr_settype import platform.posix.pthread_mutexattr_t /** - * A simple lock implementation in Linux. - * Implementations of this class should be re-entrant. - * @author yaqiao + * Linux platform lock implementation using pthread recursive mutex. + * + * @author Yuang Qiao */ - @OptIn(ExperimentalForeignApi::class) internal actual class Lock actual constructor() { diff --git a/sqllin-driver/src/linuxMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsLinux.kt b/sqllin-driver/src/linuxMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsLinux.kt index c3a6be3..6ad08f8 100644 --- a/sqllin-driver/src/linuxMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsLinux.kt +++ b/sqllin-driver/src/linuxMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsLinux.kt @@ -22,12 +22,13 @@ import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.toKString /** - * The tools with Linux implementation - * @author yaqiao + * Converts C byte pointer to String using toKString on Linux. */ - @OptIn(ExperimentalForeignApi::class) internal actual fun bytesToString(bv: CPointer): String = bv.toKString() +/** + * Linux file path separator (forward slash). + */ internal actual inline val separatorChar: Char get() = '/' \ No newline at end of file diff --git a/sqllin-driver/src/linuxTest/kotlin/com/ctrip/sqllin/driver/PlatformLinux.kt b/sqllin-driver/src/linuxTest/kotlin/com/ctrip/sqllin/driver/PlatformLinux.kt index 4aafb76..ee01438 100644 --- a/sqllin-driver/src/linuxTest/kotlin/com/ctrip/sqllin/driver/PlatformLinux.kt +++ b/sqllin-driver/src/linuxTest/kotlin/com/ctrip/sqllin/driver/PlatformLinux.kt @@ -22,7 +22,7 @@ import platform.posix.getcwd /** * Linux platform-related functions - * @author yaqiao + * @author Yuang Qiao */ @OptIn(ExperimentalForeignApi::class) diff --git a/sqllin-driver/src/mingwMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt b/sqllin-driver/src/mingwMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt index 27857cc..7e76f48 100644 --- a/sqllin-driver/src/mingwMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt +++ b/sqllin-driver/src/mingwMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt @@ -33,11 +33,10 @@ import platform.posix.pthread_mutexattr_settype import platform.posix.pthread_mutexattr_tVar /** - * A simple lock implementation in MinGW. - * Implementations of this class should be re-entrant. - * @author yaqiao + * MinGW/Windows platform lock implementation using pthread recursive mutex. + * + * @author Yuang Qiao */ - @OptIn(ExperimentalForeignApi::class) internal actual class Lock actual constructor() { diff --git a/sqllin-driver/src/mingwMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsMinGW.kt b/sqllin-driver/src/mingwMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsMinGW.kt index 9124a4a..3d27b92 100644 --- a/sqllin-driver/src/mingwMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsMinGW.kt +++ b/sqllin-driver/src/mingwMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsMinGW.kt @@ -22,12 +22,13 @@ import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.toKStringFromUtf8 /** - * The tools with Windows implementation - * @author yaqiao + * Converts C byte pointer to String using toKStringFromUtf8 on Windows (MinGW). */ - @OptIn(ExperimentalForeignApi::class) internal actual fun bytesToString(bv: CPointer): String = bv.toKStringFromUtf8() +/** + * Windows file path separator (backslash). + */ internal actual inline val separatorChar: Char get() = '\\' \ No newline at end of file diff --git a/sqllin-driver/src/mingwTest/kotlin/com/ctrip/sqllin/driver/PlatformMingw.kt b/sqllin-driver/src/mingwTest/kotlin/com/ctrip/sqllin/driver/PlatformMingw.kt index 1577c68..02866e0 100644 --- a/sqllin-driver/src/mingwTest/kotlin/com/ctrip/sqllin/driver/PlatformMingw.kt +++ b/sqllin-driver/src/mingwTest/kotlin/com/ctrip/sqllin/driver/PlatformMingw.kt @@ -22,7 +22,7 @@ import platform.posix._wgetcwd /** * Windows platform-related functions * The doc of _getcwd: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getcwd-wgetcwd?view=msvc-170 - * @author yaqiao + * @author Yuang Qiao */ @OptIn(ExperimentalForeignApi::class) diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt index 5faee17..a91acda 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt @@ -20,10 +20,12 @@ import com.ctrip.sqllin.driver.platform.Lock import com.ctrip.sqllin.driver.platform.withLock /** - * The concurrent database connection, use platform-related lock to ensure thread-safe - * @author yaqiao + * Thread-safe wrapper for [NativeDatabaseConnection] using platform-specific locks. + * + * Delegates all operations to the underlying connection while ensuring thread safety. + * + * @author Yuang Qiao */ - internal class ConcurrentDatabaseConnection( private val delegateConnection: NativeDatabaseConnection ) : NativeDatabaseConnection() { diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt index 6221fbe..7dac435 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentStatement.kt @@ -20,11 +20,12 @@ import com.ctrip.sqllin.driver.platform.Lock import com.ctrip.sqllin.driver.platform.withLock /** - * The concurrent statement, use platform-related lock to ensure thread-safe, - * the lock come from the ConcurrentDatabaseConnection - * @author yaqiao + * Thread-safe wrapper for [SQLiteStatement] using platform-specific locks. + * + * Ensures thread safety by delegating all operations through the provided lock. + * + * @author Yuang Qiao */ - internal class ConcurrentStatement( private val delegateStatement: SQLiteStatement, private val accessLock: Lock, diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ExtensionNative.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ExtensionNative.kt index 09c819e..a1f0e00 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ExtensionNative.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ExtensionNative.kt @@ -22,10 +22,8 @@ import com.ctrip.sqllin.driver.platform.withLock import platform.posix.remove /** - * SQLite extension Native - * @author yaqiao + * Converts a String path to a [DatabasePath] for native platforms. */ - public fun String.toDatabasePath(): DatabasePath = StringDatabasePath(this) private val connectionCreationLock = Lock() diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt index efa7ca6..f9c4a73 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt @@ -17,10 +17,10 @@ package com.ctrip.sqllin.driver /** - * SQLite Cursor Native actual - * @author yaqiao + * Native implementation of [CommonCursor] backed by a [SQLiteStatement]. + * + * @author Yuang Qiao */ - internal class NativeCursor( private val statement: SQLiteStatement ) : CommonCursor { diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeDatabaseConnection.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeDatabaseConnection.kt index 94ed467..2674a9f 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeDatabaseConnection.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeDatabaseConnection.kt @@ -17,14 +17,22 @@ package com.ctrip.sqllin.driver /** - * The super class for DatabaseConnection on native platforms - * @author yaqiao + * Base class for native platform database connections. + * + * Handles parameter binding for SQLiteStatement with support for Kotlin native types. + * + * @author Yuang Qiao */ - internal abstract class NativeDatabaseConnection : DatabaseConnection { + /** + * Creates a SQLiteStatement for the given SQL. + */ abstract fun createStatement(sql: String): SQLiteStatement + /** + * Binds parameters to a SQLiteStatement, supporting common Kotlin types. + */ protected fun bindParamsToSQL(sql: String, bindParams: Array?): SQLiteStatement = createStatement(sql).apply { bindParams?.run { require(isNotEmpty()) { "Empty bindArgs" } diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/RealDatabaseConnection.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/RealDatabaseConnection.kt index d9b3175..a03c298 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/RealDatabaseConnection.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/RealDatabaseConnection.kt @@ -23,10 +23,12 @@ import kotlin.concurrent.AtomicInt import kotlin.concurrent.AtomicReference /** - * Database manager Native actual - * @author yaqiao + * Native implementation of [DatabaseConnection] using C interop with SQLite. + * + * Provides direct access to SQLite C API on native platforms with transaction support. + * + * @author Yuang Qiao */ - internal class RealDatabaseConnection( private val database: NativeDatabase ) : NativeDatabaseConnection() { diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteResultCode.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteResultCode.kt index 4f8dbb7..61543e9 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteResultCode.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteResultCode.kt @@ -20,7 +20,8 @@ import com.ctrip.sqllin.driver.SQLiteResultCode.Companion.INVALID_CODE import com.ctrip.sqllin.driver.cinterop.SQLiteErrorType /** - * The result codes in SQLite + * SQLite exception with native result code information. + * * @author Yuang Qiao */ public class SQLiteResultCode(message: String, resultCode: Int) : SQLiteException( @@ -35,6 +36,9 @@ public class SQLiteResultCode(message: String, resultCode: Int) : SQLiteExceptio } } +/** + * Creates a SQLiteException with optional error code. + */ internal fun sqliteException(message: String, errorCode: Int = INVALID_CODE): SQLiteException = if (errorCode == INVALID_CODE) SQLiteException(message) diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt index c968129..75cdfd5 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt @@ -17,10 +17,12 @@ package com.ctrip.sqllin.driver /** - * SQLite native statement abstract APIs - * @author yaqiao + * Interface for native SQLite prepared statements. + * + * Provides low-level access to statement execution, parameter binding, and result retrieval. + * + * @author Yuang Qiao */ - internal interface SQLiteStatement { fun isNull(columnIndex: Int): Boolean diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeDatabase.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeDatabase.kt index c9648be..f6435d3 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeDatabase.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/NativeDatabase.kt @@ -24,14 +24,19 @@ import com.ctrip.sqllin.sqlite3.* import kotlinx.cinterop.* /** - * The native database wrapper for `sqlite3`, interop with SQLite C APIs directly - * @author yaqiao + * Native wrapper for sqlite3 database handle. + * + * Provides direct C interop with SQLite3 APIs for database operations on native platforms. + * + * @author Yuang Qiao */ - @OptIn(ExperimentalForeignApi::class) internal class NativeDatabase private constructor(val dbPointer: CPointer) { companion object { + /** + * Opens a native SQLite database with the given configuration. + */ fun openNativeDatabase(configuration: DatabaseConfiguration, realPath: String): NativeDatabase { val sqliteFlags = SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE or SQLITE_OPEN_URI @@ -75,6 +80,9 @@ internal class NativeDatabase private constructor(val dbPointer: CPointer>() @@ -91,6 +99,9 @@ internal class NativeDatabase private constructor(val dbPointer: CPointer Lock.withLock(block: () -> T): T { lock() try { diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsNative.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsNative.kt index 9743804..cc9557f 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsNative.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsNative.kt @@ -21,9 +21,7 @@ import kotlinx.cinterop.CPointer import kotlinx.cinterop.ExperimentalForeignApi /** - * The tools with platform-specific implementation - * @author yaqiao + * Converts C byte pointer to Kotlin String. */ - @OptIn(ExperimentalForeignApi::class) internal expect fun bytesToString(bv: CPointer): String diff --git a/sqllin-driver/src/nativeTest/kotlin/com/ctrip/sqllin/driver/NativeTest.kt b/sqllin-driver/src/nativeTest/kotlin/com/ctrip/sqllin/driver/NativeTest.kt index 893f1e8..99c5e8a 100644 --- a/sqllin-driver/src/nativeTest/kotlin/com/ctrip/sqllin/driver/NativeTest.kt +++ b/sqllin-driver/src/nativeTest/kotlin/com/ctrip/sqllin/driver/NativeTest.kt @@ -21,7 +21,7 @@ import kotlin.test.Test /** * Native unit test - * @author yaqiao + * @author Yuang Qiao */ class NativeTest { diff --git a/sqllin-driver/src/nativeTest/kotlin/com/ctrip/sqllin/driver/Platform.kt b/sqllin-driver/src/nativeTest/kotlin/com/ctrip/sqllin/driver/Platform.kt index a43b576..4af84f3 100644 --- a/sqllin-driver/src/nativeTest/kotlin/com/ctrip/sqllin/driver/Platform.kt +++ b/sqllin-driver/src/nativeTest/kotlin/com/ctrip/sqllin/driver/Platform.kt @@ -18,7 +18,7 @@ package com.ctrip.sqllin.driver /** * Some platform-related functions - * @author yaqiao + * @author Yuang Qiao */ /** diff --git a/sqllin-dsl-test/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/test/AndroidTest.kt b/sqllin-dsl-test/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/test/AndroidTest.kt index f2003a6..ad448a1 100644 --- a/sqllin-dsl-test/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/test/AndroidTest.kt +++ b/sqllin-dsl-test/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/test/AndroidTest.kt @@ -12,7 +12,7 @@ import org.junit.runner.RunWith /** * Android instrumented test - * @author yaqiao + * @author Yuang Qiao */ @RunWith(AndroidJUnit4ClassRunner::class) diff --git a/sqllin-dsl-test/src/appleTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformApple.kt b/sqllin-dsl-test/src/appleTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformApple.kt index aa056a6..8b34e0a 100644 --- a/sqllin-dsl-test/src/appleTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformApple.kt +++ b/sqllin-dsl-test/src/appleTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformApple.kt @@ -23,7 +23,7 @@ import platform.Foundation.NSUserDomainMask /** * Apple platform-related functions - * @author yaqiao + * @author Yuang Qiao */ @OptIn(UnsafeNumber::class) diff --git a/sqllin-dsl-test/src/commonMain/kotlin/com/ctrip/sqllin/dsl/test/Entities.kt b/sqllin-dsl-test/src/commonMain/kotlin/com/ctrip/sqllin/dsl/test/Entities.kt index 98ff08d..3e8d24e 100644 --- a/sqllin-dsl-test/src/commonMain/kotlin/com/ctrip/sqllin/dsl/test/Entities.kt +++ b/sqllin-dsl-test/src/commonMain/kotlin/com/ctrip/sqllin/dsl/test/Entities.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.Serializable /** * Book entity - * @author yaqiao + * @author Yuang Qiao */ @DBRow("book") diff --git a/sqllin-dsl-test/src/commonMain/kotlin/com/ctrip/sqllin/dsl/test/TestPrimitiveTypeForKSP.kt b/sqllin-dsl-test/src/commonMain/kotlin/com/ctrip/sqllin/dsl/test/TestPrimitiveTypeForKSP.kt index 9544853..c877e61 100644 --- a/sqllin-dsl-test/src/commonMain/kotlin/com/ctrip/sqllin/dsl/test/TestPrimitiveTypeForKSP.kt +++ b/sqllin-dsl-test/src/commonMain/kotlin/com/ctrip/sqllin/dsl/test/TestPrimitiveTypeForKSP.kt @@ -22,7 +22,7 @@ import kotlinx.serialization.Transient /** * Test whether the sqllin-processor could generate primitive type and String correctly - * @author yaqiao + * @author Yuang Qiao */ @DBRow diff --git a/sqllin-dsl-test/src/commonTest/kotlin/com/ctrip/sqllin/dsl/test/CommonBasicTest.kt b/sqllin-dsl-test/src/commonTest/kotlin/com/ctrip/sqllin/dsl/test/CommonBasicTest.kt index 94e0d5a..dfba999 100644 --- a/sqllin-dsl-test/src/commonTest/kotlin/com/ctrip/sqllin/dsl/test/CommonBasicTest.kt +++ b/sqllin-dsl-test/src/commonTest/kotlin/com/ctrip/sqllin/dsl/test/CommonBasicTest.kt @@ -34,7 +34,7 @@ import kotlin.test.assertNotEquals /** * The sqllin-dsl common test - * @author yaqiao + * @author Yuang Qiao */ class CommonBasicTest(private val path: DatabasePath) { diff --git a/sqllin-dsl-test/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/test/JvmTest.kt b/sqllin-dsl-test/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/test/JvmTest.kt index 1f630d8..9527486 100644 --- a/sqllin-dsl-test/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/test/JvmTest.kt +++ b/sqllin-dsl-test/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/test/JvmTest.kt @@ -8,7 +8,7 @@ import kotlin.test.Test /** * Native unit test - * @author yaqiao + * @author Yuang Qiao */ class JvmTest { diff --git a/sqllin-dsl-test/src/linuxTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformLinux.kt b/sqllin-dsl-test/src/linuxTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformLinux.kt index 91348a3..ef77712 100644 --- a/sqllin-dsl-test/src/linuxTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformLinux.kt +++ b/sqllin-dsl-test/src/linuxTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformLinux.kt @@ -22,7 +22,7 @@ import platform.posix.getcwd /** * Linux platform-related functions - * @author yaqiao + * @author Yuang Qiao */ @OptIn(ExperimentalForeignApi::class) diff --git a/sqllin-dsl-test/src/mingwTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformMingw.kt b/sqllin-dsl-test/src/mingwTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformMingw.kt index a87f013..4898131 100644 --- a/sqllin-dsl-test/src/mingwTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformMingw.kt +++ b/sqllin-dsl-test/src/mingwTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformMingw.kt @@ -22,7 +22,7 @@ import platform.posix._wgetcwd /** * Windows platform-related functions * The doc of _wgetcwd: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getcwd-wgetcwd?view=msvc-170 - * @author yaqiao + * @author Yuang Qiao */ @OptIn(ExperimentalForeignApi::class) diff --git a/sqllin-dsl-test/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/test/NativeTest.kt b/sqllin-dsl-test/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/test/NativeTest.kt index 9b51de1..8fadbb8 100644 --- a/sqllin-dsl-test/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/test/NativeTest.kt +++ b/sqllin-dsl-test/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/test/NativeTest.kt @@ -24,7 +24,7 @@ import kotlin.test.Test /** * Native unit test - * @author yaqiao + * @author Yuang Qiao */ class NativeTest { diff --git a/sqllin-dsl-test/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/test/Platform.kt b/sqllin-dsl-test/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/test/Platform.kt index f704349..5d8ac94 100644 --- a/sqllin-dsl-test/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/test/Platform.kt +++ b/sqllin-dsl-test/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/test/Platform.kt @@ -18,7 +18,7 @@ package com.ctrip.sqllin.dsl.test /** * Some platform-related functions - * @author yaqiao + * @author Yuang Qiao */ /** diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DSLDBConfiguration.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DSLDBConfiguration.kt index d588df7..d7cbb20 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DSLDBConfiguration.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DSLDBConfiguration.kt @@ -22,10 +22,26 @@ import com.ctrip.sqllin.driver.JournalMode import com.ctrip.sqllin.driver.SynchronousMode /** - * DSL database configuration + * DSL-level database configuration with [DatabaseScope] callbacks. + * + * Similar to [DatabaseConfiguration] but allows create and upgrade callbacks + * to use the type-safe SQL DSL instead of raw [com.ctrip.sqllin.driver.DatabaseConnection] operations. + * + * @property name The database filename + * @property path The database directory path + * @property version The database schema version + * @property isReadOnly Whether to open in read-only mode. Default: false + * @property inMemory Whether to create an in-memory database. Default: false + * @property journalMode The SQLite journal mode. Default: [JournalMode.WAL] + * @property synchronousMode The SQLite synchronous mode. Default: [SynchronousMode.NORMAL] + * @property busyTimeout Timeout in milliseconds for lock waits. Default: 5000ms + * @property lookasideSlotSize Size of lookaside memory slots. Default: 0 + * @property lookasideSlotCount Number of lookaside memory slots. Default: 0 + * @property create Callback invoked when creating a new database, executed within a [DatabaseScope] + * @property upgrade Callback invoked when upgrading the schema, executed within a [DatabaseScope] + * * @author Yuang Qiao */ - public data class DSLDBConfiguration( val name: String, val path: DatabasePath, @@ -40,6 +56,9 @@ public data class DSLDBConfiguration( val create: DatabaseScope.() -> Unit = {}, val upgrade: DatabaseScope.(oldVersion: Int, newVersion: Int) -> Unit = { _, _ -> }, ) { + /** + * Converts to driver-level configuration, wrapping DSL callbacks. + */ internal infix fun convertToDatabaseConfiguration(enableSimpleSQLLog: Boolean): DatabaseConfiguration = DatabaseConfiguration( name, path, diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt index 97f2dde..fb3736d 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt @@ -21,22 +21,44 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock /** - * Database object + * High-level database interface for executing SQL operations using type-safe DSL. + * + * Database objects are created via factory functions in [DatabaseCreators.kt][Database] + * and provide a scope-based API for executing SQL statements. All SQL operations must be + * performed within a [DatabaseScope], which is entered by invoking the database object. + * + * Example: + * ```kotlin + * val database = Database(config) + * database { + * PersonTable INSERT person + * PersonTable SELECT WHERE(PersonTable.age GTE 18) + * } + * database.close() + * ``` + * * @author Yuang Qiao */ - public class Database internal constructor( private val databaseConnection: DatabaseConnection, private val enableSimpleSQLLog: Boolean = false, ) { /** - * Close the database connection. + * Closes the database connection and releases resources. + * + * After closing, the database cannot be used for further operations. */ public fun close(): Unit = databaseConnection.close() /** - * Start a scope with this database object that used for execute SQL. + * Opens a database scope for executing SQL statements. + * + * All SQL operations within the block are executed when the scope exits. + * Statements are batched and executed in order. + * + * @param block The DSL block containing SQL operations + * @return The result of the block */ public operator fun invoke(block: DatabaseScope.() -> T): T { val databaseScope = DatabaseScope(databaseConnection, enableSimpleSQLLog) @@ -47,6 +69,15 @@ public class Database internal constructor( private val executiveMutex by lazy { Mutex() } + /** + * Opens a suspendable database scope for executing SQL statements in coroutines. + * + * Similar to [invoke] but supports suspend functions within the block. + * Statement execution is serialized using a mutex to ensure thread safety. + * + * @param block The suspending DSL block containing SQL operations + * @return The result of the block + */ public suspend infix fun suspendedScope(block: suspend DatabaseScope.() -> T): T { val databaseScope = DatabaseScope(databaseConnection, enableSimpleSQLLog) val result = databaseScope.block() diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DatabaseCreators.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DatabaseCreators.kt index 19e71d8..cbaed42 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DatabaseCreators.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DatabaseCreators.kt @@ -21,15 +21,34 @@ import com.ctrip.sqllin.driver.DatabasePath import com.ctrip.sqllin.driver.openDatabase /** - * Factory functions for Database + * Factory functions for creating [Database] instances. + * * @author Yuang Qiao */ +/** + * Creates a database from a driver-level configuration. + * + * @param configuration The database configuration + * @param enableSimpleSQLLog Whether to enable simple SQL logging for debugging + * @return A new database instance + */ public fun Database( configuration: DatabaseConfiguration, enableSimpleSQLLog: Boolean = false, ): Database = Database(openDatabase(configuration), enableSimpleSQLLog) +/** + * Creates a database with basic configuration parameters. + * + * Uses default settings for other configuration options. + * + * @param name The database filename + * @param path The database directory path + * @param version The database schema version + * @param enableSimpleSQLLog Whether to enable simple SQL logging for debugging + * @return A new database instance + */ public fun Database( name: String, path: DatabasePath, @@ -44,6 +63,16 @@ public fun Database( enableSimpleSQLLog ) +/** + * Creates a database from a DSL-level configuration. + * + * Allows using [DatabaseScope] in create and upgrade callbacks instead of + * raw [com.ctrip.sqllin.driver.DatabaseConnection]. + * + * @param dsldbConfiguration The DSL database configuration + * @param enableSimpleSQLLog Whether to enable simple SQL logging for debugging + * @return A new database instance + */ public fun Database( dsldbConfiguration: DSLDBConfiguration, enableSimpleSQLLog: Boolean = false, diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DatabaseScope.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DatabaseScope.kt index d112108..201ba0e 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DatabaseScope.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/DatabaseScope.kt @@ -35,19 +35,43 @@ import kotlin.concurrent.Volatile import kotlin.jvm.JvmName /** - * The database scope, it's used to restrict the scope that write DSL SQL statements + * Scope for executing type-safe SQL DSL statements. + * + * DatabaseScope provides extension functions on [Table] objects that enable SQL operations + * using Kotlin DSL syntax. All SQL statements written within this scope are collected and + * executed in batch when the scope exits. + * + * Supported operations: + * - **INSERT**: Add entities to tables + * - **UPDATE**: Modify existing records with SET and WHERE clauses + * - **DELETE**: Remove records with WHERE clauses + * - **SELECT**: Query records with WHERE, ORDER BY, LIMIT, GROUP BY, JOIN, and UNION + * - **CREATE**: Create tables from data class definitions + * + * Transaction support: + * - Use [transaction] to execute multiple statements atomically + * - Transactions can be nested and are automatically committed or rolled back + * + * Example: + * ```kotlin + * database { + * transaction { + * PersonTable INSERT person + * PersonTable UPDATE SET { name = "Alice" } WHERE (age GTE 18) + * } + * val adults = PersonTable SELECT WHERE(age GTE 18) LIMIT 10 + * } + * ``` + * * @author Yuang Qiao */ - @Suppress("UNCHECKED_CAST") public class DatabaseScope internal constructor( private val databaseConnection: DatabaseConnection, private val enableSimpleSQLLog: Boolean, ) { - /** - * Transaction. - */ + // ========== Transaction Management ========== @Volatile private var transactionStatementsGroup: TransactionStatementsGroup? = null @@ -55,6 +79,11 @@ public class DatabaseScope internal constructor( private inline val isInTransaction get() = transactionStatementsGroup != null + /** + * Begins a new transaction. + * + * @return `true` if transaction started successfully, `false` if already in a transaction + */ public fun beginTransaction(): Boolean { if (isInTransaction) return false @@ -63,10 +92,25 @@ public class DatabaseScope internal constructor( return true } + /** + * Ends the current transaction. + * + * The transaction will be committed or rolled back based on whether + * [endTransaction] was called. + */ public fun endTransaction() { transactionStatementsGroup = null } + /** + * Executes a block of SQL statements as a single transaction. + * + * If the block completes successfully, the transaction is committed. + * If an exception is thrown, the transaction is rolled back. + * + * @param block The block of SQL statements to execute + * @return The result of the block + */ public inline fun transaction(block: DatabaseScope.() -> T): T { beginTransaction() try { @@ -76,9 +120,7 @@ public class DatabaseScope internal constructor( } } - /** - * SQL execute. - */ + // ========== Statement Execution Management ========== private val executiveEngine = DatabaseExecuteEngine(enableSimpleSQLLog) @@ -98,21 +140,74 @@ public class DatabaseScope internal constructor( internal fun executeAllStatements() = executiveEngine.executeAllStatement() + // ========== INSERT Operations ========== + /** - * Insert. + * Inserts multiple entities into the table, allowing the database to auto-generate primary keys. + * + * For entities with `Long?` primary keys annotated with [@PrimaryKey][com.ctrip.sqllin.dsl.annotation.PrimaryKey], + * set the ID property to `null` to let SQLite automatically generate the ID. If you need to insert + * entities with pre-defined IDs (e.g., during data migration), use [INSERT_WITH_ID] instead. + * + * Example: + * ```kotlin + * val person = Person(id = null, name = "Alice", age = 25) // ID will be auto-generated + * PersonTable INSERT listOf(person1, person2) + * ``` + * + * @see INSERT_WITH_ID for inserting with pre-defined primary key values */ - @StatementDslMaker public infix fun Table.INSERT(entities: Iterable) { val statement = Insert.insert(this, databaseConnection, entities) addStatement(statement) } + /** + * Inserts a single entity into the table, allowing the database to auto-generate the primary key. + * + * For entities with `Long?` primary keys annotated with [@PrimaryKey][com.ctrip.sqllin.dsl.annotation.PrimaryKey], + * set the ID property to `null` to let SQLite automatically generate the ID. + * + * Example: + * ```kotlin + * val person = Person(id = null, name = "Alice", age = 25) // ID will be auto-generated + * PersonTable INSERT person + * ``` + * + * @see INSERT_WITH_ID for inserting with a pre-defined primary key value + */ @StatementDslMaker public infix fun Table.INSERT(entity: T): Unit = INSERT(listOf(entity)) - + /** + * Inserts multiple entities with pre-defined primary key values (advanced API). + * + * **⚠️ This is an advanced API for special use cases like data migration or testing.** + * Use this function when you need to manually specify the primary key ID instead of letting + * the database auto-generate it. For normal inserts where the database should generate IDs + * automatically, use [INSERT] instead. + * + * This function is particularly useful for: + * - Data migration from another database where you need to preserve existing IDs + * - Testing scenarios where you need predictable, specific ID values + * - Restoring backup data with original IDs + * + * Example: + * ```kotlin + * @OptIn(AdvancedInsertAPI::class) + * fun migrateData() { + * val person = Person(id = 12345L, name = "Alice", age = 25) // Use specific ID + * PersonTable INSERT_WITH_ID listOf(person1, person2) + * } + * ``` + * + * **Important**: This function requires explicit opt-in via `@OptIn(AdvancedInsertAPI::class)` + * to acknowledge that you understand the implications of manually specifying primary keys. + * + * @see INSERT for standard inserts with auto-generated IDs + */ @AdvancedInsertAPI @StatementDslMaker public infix fun Table.INSERT_WITH_ID(entities: Iterable) { @@ -120,15 +215,42 @@ public class DatabaseScope internal constructor( addStatement(statement) } + /** + * Inserts a single entity with a pre-defined primary key value (advanced API). + * + * **⚠️ This is an advanced API for special use cases like data migration or testing.** + * Use this function when you need to manually specify the primary key ID. For normal inserts, + * use [INSERT] instead. + * + * Example: + * ```kotlin + * @OptIn(AdvancedInsertAPI::class) + * fun migrateData() { + * val person = Person(id = 12345L, name = "Alice", age = 25) // Use specific ID + * PersonTable INSERT_WITH_ID person + * } + * ``` + * + * @see INSERT for standard inserts with auto-generated IDs + * @see INSERT_WITH_ID for batch inserts with pre-defined IDs + */ @AdvancedInsertAPI @StatementDslMaker public infix fun Table.INSERT_WITH_ID(entity: T): Unit = INSERT_WITH_ID(listOf(entity)) + // ========== UPDATE Operations ========== + /** - * Update. + * Updates records in the table with SET clause. + * + * Can be followed by WHERE to target specific records. + * + * Example: + * ```kotlin + * PersonTable UPDATE SET { name = "Alice" } WHERE (age GTE 18) + * ``` */ - @StatementDslMaker public infix fun Table.UPDATE(clause: SetClause): UpdateStatementWithoutWhereClause = transactionStatementsGroup?.let { @@ -139,34 +261,53 @@ public class DatabaseScope internal constructor( executiveEngine addStatement it } + // ========== DELETE Operations ========== + /** - * Delete. + * Deletes all records from the table. + * + * Example: + * ```kotlin + * PersonTable DELETE X + * ``` */ - @StatementDslMaker public infix fun Table<*>.DELETE(x: X) { val statement = Delete.deleteAllEntities(this, databaseConnection) addStatement(statement) } + /** + * Deletes records matching the WHERE clause. + * + * Example: + * ```kotlin + * PersonTable DELETE WHERE(age LT 18) + * ``` + */ @StatementDslMaker public infix fun Table.DELETE(clause: WhereClause) { val statement = Delete.delete(this, databaseConnection, clause) addStatement(statement) } - /** - * Select. - */ + // ========== SELECT Operations ========== /** - * Select with no any clause. + * Selects all records from the table. + * + * Example: + * ```kotlin + * val people = PersonTable SELECT X + * ``` */ - @StatementDslMaker public inline infix fun Table.SELECT(x: X): FinalSelectStatement = select(kSerializer(), false) + /** + * Selects distinct records from the table. + */ @StatementDslMaker public inline infix fun Table.SELECT_DISTINCT(x: X): FinalSelectStatement = select(kSerializer(), true) @@ -179,9 +320,15 @@ public class DatabaseScope internal constructor( } /** - * Receive the 'WHERE' clause. + * Selects records matching the WHERE clause. + * + * Can be followed by ORDER BY, LIMIT, etc. + * + * Example: + * ```kotlin + * val adults = PersonTable SELECT WHERE(age GTE 18) + * ``` */ - @StatementDslMaker public inline infix fun Table.SELECT(clause: WhereClause): WhereSelectStatement = select(kSerializer(), clause, false) @@ -198,9 +345,13 @@ public class DatabaseScope internal constructor( } /** - * Receive the 'ORDER BY' clause. + * Selects records with ORDER BY clause. + * + * Example: + * ```kotlin + * val sorted = PersonTable SELECT ORDER_BY(age.DESC()) + * ``` */ - @StatementDslMaker public inline infix fun Table.SELECT(clause: OrderByClause): OrderBySelectStatement = select(kSerializer(), clause, false) @@ -217,9 +368,13 @@ public class DatabaseScope internal constructor( } /** - * Receive the 'LIMIT' clause. + * Selects a limited number of records. + * + * Example: + * ```kotlin + * val first10 = PersonTable SELECT LIMIT(0, 10) + * ``` */ - @StatementDslMaker public inline infix fun Table.SELECT(clause: LimitClause): LimitSelectStatement = select(kSerializer(), clause, false) @@ -236,9 +391,13 @@ public class DatabaseScope internal constructor( } /** - * Receive the 'GROUP BY' clause. + * Selects records with GROUP BY clause. + * + * Example: + * ```kotlin + * val grouped = PersonTable SELECT GROUP_BY(age) + * ``` */ - @StatementDslMaker public inline infix fun Table.SELECT(clause: GroupByClause): GroupBySelectStatement = select(kSerializer(), clause, false) @@ -254,16 +413,28 @@ public class DatabaseScope internal constructor( return statement } - public inline fun getKSerializer(): KSerializer = EmptySerializersModule().serializer() - /** - * The 'UNION' clause of Select. + * Gets the KSerializer for the reified type parameter. */ + public inline fun getKSerializer(): KSerializer = EmptySerializersModule().serializer() + + // ========== UNION Operations ========== private val unionSelectStatementGroupStack by lazy { ArrayDeque>() } private fun getSelectStatementGroup(): StatementContainer = unionSelectStatementGroupStack.lastOrNull() ?: transactionStatementsGroup ?: executiveEngine + /** + * Combines multiple SELECT statements with UNION (removes duplicates). + * + * Example: + * ```kotlin + * val combined = PersonTable.UNION { + * it SELECT WHERE(age LT 18) + * it SELECT WHERE(age GTE 65) + * } + * ``` + */ public inline fun Table.UNION(block: Table.(Table) -> Unit): FinalSelectStatement { beginUnion() var selectStatement: SelectStatement? = null @@ -276,6 +447,9 @@ public class DatabaseScope internal constructor( } } + /** + * Combines multiple SELECT statements with UNION ALL (keeps duplicates). + */ @StatementDslMaker public inline fun Table.UNION_ALL(block: Table.(Table) -> Unit): FinalSelectStatement { beginUnion() @@ -289,24 +463,39 @@ public class DatabaseScope internal constructor( } } + /** + * Begins a UNION statement group (for advanced usage). + */ public fun beginUnion() { unionSelectStatementGroupStack.add(UnionSelectStatementGroup()) } + /** + * Creates the final UNION select statement from accumulated SELECT statements. + */ public fun createUnionSelectStatement(isUnionAll: Boolean): FinalSelectStatement { check(unionSelectStatementGroupStack.isNotEmpty()) { "Please invoke the 'beginUnion' before you invoke this function!!!" } return (unionSelectStatementGroupStack.last() as UnionSelectStatementGroup).unionStatements(isUnionAll) } + /** + * Ends the UNION statement group and adds the final statement. + */ public fun endUnion(selectStatement: SelectStatement?) { unionSelectStatementGroupStack.removeLast() selectStatement?.let { addSelectStatement(it) } } + // ========== JOIN Operations ========== + /** - * Receive the 'JOIN' clause. + * Selects with JOIN clause (requires ON condition). + * + * Example: + * ```kotlin + * val joined = PersonTable SELECT INNER_JOIN(AddressTable) ON ... + * ``` */ - @StatementDslMaker public inline infix fun Table.SELECT(clause: JoinClause): JoinStatementWithoutCondition = select(getKSerializer(), clause, false) @@ -321,9 +510,13 @@ public class DatabaseScope internal constructor( } /** - * Receive the natural join clause(includes 'NATURAL LEFT OUTER JOIN' and 'NATURAL INNER JOIN'). + * Selects with NATURAL JOIN (joins on columns with same names). + * + * Example: + * ```kotlin + * val joined = PersonTable SELECT NATURAL_INNER_JOIN(AddressTable) + * ``` */ - @StatementDslMaker public inline infix fun Table.SELECT(clause: NaturalJoinClause): JoinSelectStatement = select(getKSerializer(), clause, false) @@ -339,8 +532,17 @@ public class DatabaseScope internal constructor( return statement } + // ========== CREATE Operations ========== + /** - * CREATE + * Creates a table from its Table definition. + * + * Example: + * ```kotlin + * CREATE(PersonTable) + * // or + * PersonTable.CREATE() + * ``` */ @StatementDslMaker public infix fun CREATE(table: Table) { @@ -348,6 +550,9 @@ public class DatabaseScope internal constructor( addStatement(statement) } + /** + * Creates this table from its definition (extension function variant). + */ @StatementDslMaker @JvmName("create") public fun Table.CREATE(): Unit = CREATE(this) diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/annotation/DBRow.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/annotation/DBRow.kt index 5e0c631..16d39d5 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/annotation/DBRow.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/annotation/DBRow.kt @@ -17,10 +17,17 @@ package com.ctrip.sqllin.dsl.annotation /** - * Annotation for where property + * Marks a data class as a SQLite table representation. + * + * This annotation is processed by sqllin-processor at compile time to generate + * a class that represents a SQLite table. The annotated data class properties + * are mapped to table columns. + * + * @property tableName The name of the SQLite table. If not specified or empty, + * the name of the annotated class will be used as the table name. + * * @author Yuang Qiao */ - @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) public annotation class DBRow(val tableName: String = "") \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/annotation/DslMaker.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/annotation/DslMaker.kt index dee2613..faea3ce 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/annotation/DslMaker.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/annotation/DslMaker.kt @@ -17,25 +17,49 @@ package com.ctrip.sqllin.dsl.annotation /** - * Dsl maker annotations + * DSL marker for SQL statement functions to prevent implicit receiver nesting. + * + * Applied to top-level SQL statement functions (SELECT, INSERT, UPDATE, DELETE). + * * @author Yuang Qiao */ - @DslMarker @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.BINARY) -public annotation class StatementDslMaker +internal annotation class StatementDslMaker +/** + * DSL marker for SQL keyword classes and properties to prevent implicit receiver nesting. + * + * Applied to SQL keyword constructs (WHERE, ORDER BY, etc.) and their properties. + * + * @author Yuang Qiao + */ @DslMarker @Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.BINARY) -public annotation class KeyWordDslMaker +internal annotation class KeyWordDslMaker +/** + * DSL marker for SQL function builders to prevent implicit receiver nesting. + * + * Applied to SQL function builder functions (aggregate functions, etc.). + * + * @author Yuang Qiao + */ @DslMarker @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.BINARY) -public annotation class FunctionDslMaker +internal annotation class FunctionDslMaker +/** + * DSL marker for generated column name properties. + * + * This annotation is applied by sqllin-processor to generated table column properties. + * **Do not use this annotation manually** - it is intended for code generation only. + * + * @author Yuang Qiao + */ @DslMarker @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.BINARY) diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/PrimaryKeyInfo.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/PrimaryKeyInfo.kt index ac44557..07be95d 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/PrimaryKeyInfo.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/PrimaryKeyInfo.kt @@ -17,10 +17,37 @@ package com.ctrip.sqllin.dsl.sql /** - * Describe the information of primary key(s) + * Metadata describing a table's primary key configuration. + * + * This class captures information extracted from [@PrimaryKey][com.ctrip.sqllin.dsl.annotation.PrimaryKey] + * and [@CompositePrimaryKey][com.ctrip.sqllin.dsl.annotation.CompositePrimaryKey] annotations + * during code generation. It enables the DSL to properly handle INSERT and UPDATE operations + * with respect to primary key constraints. + * + * **Single Primary Key:** + * When a table has a single primary key column (marked with `@PrimaryKey`): + * - [primaryKeyName] contains the column name + * - [compositePrimaryKeys] is `null` + * - [isRowId] is `true` if the key is a `Long?` type (maps to SQLite's INTEGER PRIMARY KEY/rowid) + * - [isAutomaticIncrement] is `true` if `@PrimaryKey(isAutoincrement = true)` was specified + * + * **Composite Primary Key:** + * When a table has multiple primary key columns (marked with `@CompositePrimaryKey`): + * - [primaryKeyName] is `null` + * - [compositePrimaryKeys] contains the list of column names forming the composite key + * - [isRowId] is `false` (composite keys cannot use rowid alias) + * - [isAutomaticIncrement] is `false` (composite keys cannot auto-increment) + * + * **No Primary Key:** + * When a table has no primary key annotations, [Table.primaryKeyInfo] is `null`. + * + * @property primaryKeyName The name of the single primary key column, or `null` for composite keys + * @property isAutomaticIncrement Whether the primary key uses SQLite's AUTOINCREMENT keyword + * @property isRowId Whether the primary key is a `Long?` type that maps to SQLite's rowid + * @property compositePrimaryKeys List of column names forming a composite primary key, or `null` for single keys + * * @author Yuang Qiao */ - public class PrimaryKeyInfo( internal val primaryKeyName: String?, internal val isAutomaticIncrement: Boolean, diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/Table.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/Table.kt index 6b7b38a..d2fa3a0 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/Table.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/Table.kt @@ -19,14 +19,58 @@ package com.ctrip.sqllin.dsl.sql import kotlinx.serialization.KSerializer /** - * SQL table + * Abstract base class representing a SQLite table with type-safe DSL operations. + * + * Table objects are typically generated by the sqllin-processor KSP plugin for data classes + * annotated with [@DBRow][com.ctrip.sqllin.dsl.annotation.DBRow]. Each generated table object + * (named `{ClassName}Table`) extends this class and provides: + * + * - Type-safe column property accessors for building SQL clauses + * - Serialization support via kotlinx.serialization + * - Primary key metadata for INSERT/UPDATE operations + * + * Table objects are used as receivers for DSL operations within [com.ctrip.sqllin.dsl.DatabaseScope]: + * + * Example: + * ```kotlin + * @Serializable + * @DBRow + * data class Person(val id: Long?, val name: String, val age: Int) + * + * // Generated by processor: object PersonTable : Table("Person") { ... } + * + * database { + * PersonTable INSERT person + * PersonTable SELECT WHERE(PersonTable.age GTE 18) + * } + * ``` + * + * @param T The entity type this table represents + * @property tableName The name of the SQLite table + * * @author Yuang Qiao */ - public abstract class Table( internal val tableName: String, ) { + /** + * Returns the kotlinx.serialization serializer for the entity type. + * + * Used internally for serializing entities to SQL INSERT/UPDATE statements + * and deserializing query results to entity objects. + */ public abstract fun kSerializer(): KSerializer + /** + * Metadata about the table's primary key configuration. + * + * - `null` if the table has no primary key + * - Contains information about single or composite primary keys, including + * whether the key is auto-incrementing or backed by SQLite's rowid + * + * This information is extracted from [@PrimaryKey][com.ctrip.sqllin.dsl.annotation.PrimaryKey] + * and [@CompositePrimaryKey][com.ctrip.sqllin.dsl.annotation.CompositePrimaryKey] annotations + * during code generation. + */ public abstract val primaryKeyInfo: PrimaryKeyInfo? } \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/X.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/X.kt index a44432a..6d2de9d 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/X.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/X.kt @@ -19,9 +19,22 @@ package com.ctrip.sqllin.dsl.sql import com.ctrip.sqllin.dsl.annotation.KeyWordDslMaker /** - * Express "*" in SQL - * @author yaqiao + * Represents the wildcard `*` in SQL statements. + * + * Used in DSL operations where SQL requires a wildcard or universal selector: + * - `SELECT *`: Select all columns from a table + * - `DELETE *`: Delete all records from a table + * + * Example: + * ```kotlin + * // SELECT * FROM PersonTable + * val allPeople = PersonTable SELECT X + * + * // DELETE FROM PersonTable + * PersonTable DELETE X + * ``` + * + * @author Yuang Qiao */ - @KeyWordDslMaker public object X \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/BaseJoinClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/BaseJoinClause.kt index b439e00..6cb79ad 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/BaseJoinClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/BaseJoinClause.kt @@ -22,10 +22,16 @@ import com.ctrip.sqllin.dsl.sql.statement.JoinSelectStatement import com.ctrip.sqllin.dsl.sql.statement.JoinStatementWithoutCondition /** - * SQL abstract "JOIN" clause - * @author yaqiao + * Base class for JOIN clauses in SELECT statements. + * + * Generates SQL for joining multiple tables. Different JOIN types (INNER, LEFT OUTER, CROSS, + * NATURAL) extend this class with their specific SQL keywords. + * + * @param R The result entity type after JOIN + * @param tables Tables to join + * + * @author Yuang Qiao */ - public sealed class BaseJoinClause(private vararg val tables: Table<*>) : SelectClause { internal abstract val clauseName: String @@ -41,8 +47,22 @@ public sealed class BaseJoinClause(private vararg val tables: Table<*>) : Sel } } +/** + * NATURAL JOIN clause - automatically matches columns with the same name. + * + * Does not require ON or USING condition. + * + * @param R The result entity type after JOIN + */ public sealed class NaturalJoinClause(vararg tables: Table<*>) : BaseJoinClause(*tables) +/** + * JOIN clause that requires an ON or USING condition. + * + * Returns [JoinStatementWithoutCondition] which must be completed with ON or USING. + * + * @param R The result entity type after JOIN + */ public sealed class JoinClause(vararg tables: Table<*>) : BaseJoinClause(*tables) @StatementDslMaker diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/Clause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/Clause.kt index be2ab9d..4d9483a 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/Clause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/Clause.kt @@ -17,8 +17,18 @@ package com.ctrip.sqllin.dsl.sql.clause /** - * Abstract clause, include 'where', 'update set' and more - * @author yaqiao + * Base interface for SQL clauses used in DSL operations. + * + * Marker interface for all clause types (WHERE, SET, ORDER BY, GROUP BY, HAVING, LIMIT, JOIN, etc.). + * Clauses are type-parameterized to ensure they operate on the correct entity type. + * + * Implementations include: + * - [SelectClause]: Clauses for SELECT statements (WHERE, ORDER BY, LIMIT, GROUP BY, HAVING, JOIN) + * - [SetClause]: SET clause for UPDATE statements + * - [ConditionClause]: Condition clauses for WHERE/HAVING + * + * @param T The entity type this clause operates on + * + * @author Yuang Qiao */ - public sealed interface Clause \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseBoolean.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseBoolean.kt index 3b89967..e4a602c 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseBoolean.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseBoolean.kt @@ -19,16 +19,27 @@ package com.ctrip.sqllin.dsl.sql.clause import com.ctrip.sqllin.dsl.sql.Table /** - * Clause Boolean, will be converted to number in SQL statement - * @author yaqiao + * Wrapper for Boolean column/function references in SQL clauses. + * + * Provides comparison operators for Boolean values. Since SQLite stores booleans as integers + * (0 for false, 1 for true), comparisons are translated to numeric expressions: + * - `column IS true` → `column > 0` + * - `column IS false` → `column <= 0` + * + * @author Yuang Qiao */ - public class ClauseBoolean( valueName: String, table: Table<*>, isFunction: Boolean, ) : ClauseElement(valueName, table, isFunction) { + /** + * Creates a condition comparing this Boolean column/function to a value. + * + * @param bool The Boolean value to compare against + * @return Condition expression (e.g., `column > 0` for true, `column <= 0` for false) + */ internal infix fun _is(bool: Boolean): SelectCondition { val sql = buildString { if (!isFunction) { diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseElement.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseElement.kt index 6db9961..064447c 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseElement.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseElement.kt @@ -19,10 +19,29 @@ package com.ctrip.sqllin.dsl.sql.clause import com.ctrip.sqllin.dsl.sql.Table /** - * Abstract clause element - * @author yaqiao + * Base class for elements used in SQL clauses. + * + * Represents a reference to a database column or function that can be used in clause expressions. + * Clause elements maintain their source table and whether they represent a function call. + * + * Subclasses provide type-specific wrappers: + * - [ClauseBoolean]: Boolean column/function references with comparison operators + * - [ClauseNumber]: Numeric column/function references with arithmetic and comparison operators + * - [ClauseString]: String column/function references with text comparison operators + * + * Used in: + * - WHERE/HAVING conditions + * - ORDER BY expressions + * - GROUP BY columns + * - SET assignments + * - JOIN USING clauses + * + * @property valueName The column name or function expression + * @property table The table this element belongs to + * @property isFunction Whether this represents a function call (e.g., COUNT, SUM) + * + * @author Yuang Qiao */ - public sealed class ClauseElement( internal val valueName: String, internal val table: Table<*>, diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseNumber.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseNumber.kt index 03b8b31..d2a3f13 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseNumber.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseNumber.kt @@ -19,51 +19,70 @@ package com.ctrip.sqllin.dsl.sql.clause import com.ctrip.sqllin.dsl.sql.Table /** - * The 'WHERE' and 'HAVING' clause properties - * @author yaqiao + * Wrapper for numeric column/function references in SQL clauses. + * + * Provides comparison and set operators for numeric values (Byte, Short, Int, Long, Float, Double). + * Supports comparisons against literal numbers or other numeric columns/functions. + * + * Available operators: + * - `lt`: Less than (<) + * - `lte`: Less than or equal (<=) + * - `eq`: Equals (=) - handles null with IS NULL + * - `neq`: Not equals (!=) - handles null with IS NOT NULL + * - `gt`: Greater than (>) + * - `gte`: Greater than or equal (>=) + * - `inIterable`: IN (value1, value2, ...) + * - `between`: BETWEEN start AND end + * + * @author Yuang Qiao */ - public class ClauseNumber( valueName: String, table: Table<*>, isFunction: Boolean, ) : ClauseElement(valueName, table, isFunction) { - // Less than, < + /** Less than (<) */ internal infix fun lt(number: Number): SelectCondition = appendNumber("<", number) - // Less than, append to ClauseNumber + /** Less than (<) - compare against another column/function */ internal infix fun lt(clauseNumber: ClauseNumber): SelectCondition = appendClauseNumber("<", clauseNumber) - // Less than or equals to, <= + /** Less than or equal (<=) */ internal infix fun lte(number: Number): SelectCondition = appendNumber("<=", number) - // Less than or equal to, append to ClauseNumber + /** Less than or equal (<=) - compare against another column/function */ internal infix fun lte(clauseNumber: ClauseNumber): SelectCondition = appendClauseNumber("<=", clauseNumber) - // Equals, == + /** Equals (=), or IS NULL if value is null */ internal infix fun eq(number: Number?): SelectCondition = appendNullableNumber("=", " IS", number) - // Equals, append to ClauseNumber + /** Equals (=) - compare against another column/function */ internal infix fun eq(clauseNumber: ClauseNumber): SelectCondition = appendClauseNumber("=", clauseNumber) - // Not equals to, != + /** Not equals (!=), or IS NOT NULL if value is null */ internal infix fun neq(number: Number?): SelectCondition = appendNullableNumber("!=", " IS NOT", number) - // Not equals to, append to ClauseNumber + /** Not equals (!=) - compare against another column/function */ internal infix fun neq(clauseNumber: ClauseNumber): SelectCondition = appendClauseNumber("!=", clauseNumber) - // Greater than, > + /** Greater than (>) */ internal infix fun gt(number: Number): SelectCondition = appendNumber(">", number) - // Greater, append to ClauseNumber + /** Greater than (>) - compare against another column/function */ internal infix fun gt(clauseNumber: ClauseNumber): SelectCondition = appendClauseNumber(">", clauseNumber) - // Greater or equals to, >= + /** Greater than or equal (>=) */ internal infix fun gte(number: Number): SelectCondition = appendNumber(">=", number) + /** Greater than or equal (>=) - compare against another column/function */ internal infix fun gte(clauseNumber: ClauseNumber): SelectCondition = appendClauseNumber(">=", clauseNumber) + /** + * IN operator - checks if value is in the given set. + * + * Generates: `column IN (1, 2, 3, ...)` + */ internal infix fun inIterable(numbers: Iterable): SelectCondition { val iterator = numbers.iterator() require(iterator.hasNext()) { "Param 'numbers' must not be empty!!!" } @@ -84,6 +103,11 @@ public class ClauseNumber( return SelectCondition(sql, null) } + /** + * BETWEEN operator - checks if value is within a range (inclusive). + * + * Generates: `column BETWEEN start AND end` + */ internal infix fun between(range: LongRange): SelectCondition { val sql = buildString { if (!isFunction) { diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseString.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseString.kt index 980ccfd..bd69901 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseString.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseString.kt @@ -19,30 +19,51 @@ package com.ctrip.sqllin.dsl.sql.clause import com.ctrip.sqllin.dsl.sql.Table /** - * Clause String - * @author yaqiao + * Wrapper for String column/function references in SQL clauses. + * + * Provides comparison and pattern matching operators for string values. Supports comparisons + * against literal strings or other string columns/functions. + * + * Available operators: + * - `eq`: Equals (=) - handles null with IS NULL + * - `neq`: Not equals (!=) - handles null with IS NOT NULL + * - `like`: LIKE pattern matching (case-insensitive, supports % and _ wildcards) + * - `glob`: GLOB pattern matching (case-sensitive, supports * and ? wildcards) + * + * @author Yuang Qiao */ - public class ClauseString( valueName: String, table: Table<*>, isFunction: Boolean, ) : ClauseElement(valueName, table, isFunction) { - // Equals, == + /** Equals (=), or IS NULL if value is null */ internal infix fun eq(str: String?): SelectCondition = appendString("=", " IS", str) - // Equals, append another ClauseString + /** Equals (=) - compare against another column/function */ internal infix fun eq(clauseString: ClauseString): SelectCondition = appendClauseString("=", clauseString) - // Not equals to, != + /** Not equals (!=), or IS NOT NULL if value is null */ internal infix fun neq(str: String?): SelectCondition = appendString("!=", " IS NOT", str) - // Not equal to, append another ClauseString + /** Not equals (!=) - compare against another column/function */ internal infix fun neq(clauseString: ClauseString): SelectCondition = appendClauseString("!=", clauseString) + /** + * LIKE operator - case-insensitive pattern matching. + * + * Wildcards: `%` (any characters), `_` (single character) + * Example: `"John%"` matches "John", "Johnson", etc. + */ internal infix fun like(regex: String): SelectCondition = appendRegex(" LIKE ", regex) + /** + * GLOB operator - case-sensitive pattern matching. + * + * Wildcards: `*` (any characters), `?` (single character) + * Example: `"John*"` matches "John", "Johnson", etc. (case-sensitive) + */ internal infix fun glob(regex: String): SelectCondition = appendRegex(" GLOB ", regex) private fun appendRegex(symbol: String, regex: String): SelectCondition { diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ConditionClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ConditionClause.kt index 81e2554..4270e05 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ConditionClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ConditionClause.kt @@ -19,10 +19,21 @@ package com.ctrip.sqllin.dsl.sql.clause import com.ctrip.sqllin.dsl.annotation.StatementDslMaker /** - * Abstract clause that could link conditions, include 'WHERE' and 'HAVING' - * @author yaqiao + * Base class for condition-based clauses (WHERE, HAVING). + * + * Wraps a [SelectCondition] and provides the clause name. Generates SQL in the format: + * ` CLAUSE_NAME condition` + * + * This file also provides uppercase DSL operators for building conditions: + * - Numeric: LT, LTE, EQ, NEQ, GT, GTE, IN, BETWEEN + * - String: EQ, NEQ, LIKE, GLOB + * - Boolean: IS + * - Logic: AND, OR + * + * @param T The entity type this clause operates on + * + * @author Yuang Qiao */ - public sealed class ConditionClause(private val selectCondition: SelectCondition) : SelectClause { internal abstract val clauseName: String diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/CrossJoinClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/CrossJoinClause.kt index f1a1d47..36687ce 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/CrossJoinClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/CrossJoinClause.kt @@ -20,13 +20,32 @@ import com.ctrip.sqllin.dsl.annotation.StatementDslMaker import com.ctrip.sqllin.dsl.sql.Table /** - * SQL "CROSS JOIN" clause - * @author yaqiao + * CROSS JOIN clause - returns the Cartesian product of two tables. + * + * Generates SQL: ` CROSS JOIN table` + * Does not require ON or USING condition. + * Returns every combination of rows from both tables. + * + * **Warning**: Result size = (left rows) × (right rows). Use with caution on large tables. + * + * @param R The result entity type after JOIN + * + * @author Yuang Qiao */ - internal class CrossJoinClause(vararg tables: Table<*>) : NaturalJoinClause(*tables) { override val clauseName: String = " CROSS JOIN " } +/** + * Creates a CROSS JOIN clause (no ON/USING needed). + * + * Usage: + * ```kotlin + * SELECT(color) CROSS_JOIN (size) + * // Returns all color-size combinations + * ``` + * + * **Warning**: Returns (left rows) × (right rows) results. + */ @StatementDslMaker public fun CROSS_JOIN(vararg tables: Table<*>): NaturalJoinClause = CrossJoinClause(*tables) \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/Function.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/Function.kt index 6b99fb0..aa31304 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/Function.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/Function.kt @@ -21,46 +21,90 @@ import com.ctrip.sqllin.dsl.sql.Table import com.ctrip.sqllin.dsl.sql.X /** - * SQLite functions - * @author yaqiao + * SQLite aggregate and scalar functions for use in SELECT clauses. + * + * These functions can be used in WHERE, HAVING, ORDER BY, and SELECT expressions. + * All functions return [ClauseElement] wrappers that can be compared with operators. + * + * @author Yuang Qiao */ +/** + * COUNT aggregate function - counts non-NULL values. + * + * Usage: + * ```kotlin + * SELECT(user) GROUP_BY (user.department) HAVING (count(user.id) GT 5) + * ``` + */ @FunctionDslMaker public fun Table.count(element: ClauseElement): ClauseNumber = ClauseNumber("count(${element.valueName})", this, true) +/** + * COUNT(*) aggregate function - counts all rows (including NULLs). + * + * Usage: + * ```kotlin + * SELECT(user) WHERE (count(X) GT 100) + * ``` + */ @FunctionDslMaker public fun Table.count(x: X): ClauseNumber = ClauseNumber("count(*)", this, true) +/** + * MAX aggregate function - returns maximum value. + */ @FunctionDslMaker public fun Table.max(element: ClauseElement): ClauseNumber = ClauseNumber("max(${element.valueName})", this, true) +/** + * MIN aggregate function - returns minimum value. + */ @FunctionDslMaker public fun Table.min(element: ClauseElement): ClauseNumber = ClauseNumber("min(${element.valueName})", this, true) +/** + * AVG aggregate function - returns average value. + */ @FunctionDslMaker public fun Table.avg(element: ClauseElement): ClauseNumber = ClauseNumber("avg(${element.valueName})", this, true) +/** + * SUM aggregate function - returns sum of values. + */ @FunctionDslMaker public fun Table.sum(element: ClauseElement): ClauseNumber = ClauseNumber("sum(${element.valueName})", this, true) +/** + * ABS scalar function - returns absolute value. + */ @FunctionDslMaker public fun Table.abs(number: ClauseElement): ClauseNumber = ClauseNumber("abs(${number.valueName})", this, true) +/** + * UPPER scalar function - converts string to uppercase. + */ @FunctionDslMaker public fun Table.upper(element: ClauseElement): ClauseString = ClauseString("upper(${element.valueName})", this, true) +/** + * LOWER scalar function - converts string to lowercase. + */ @FunctionDslMaker public fun Table.lower(element: ClauseElement): ClauseString = ClauseString("lower(${element.valueName})", this, true) +/** + * LENGTH scalar function - returns string/blob length in bytes. + */ @FunctionDslMaker public fun Table.length(element: ClauseElement): ClauseNumber = ClauseNumber("length(${element.valueName})", this, true) diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/GroupByClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/GroupByClause.kt index 005dad1..cf4cb1e 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/GroupByClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/GroupByClause.kt @@ -22,10 +22,19 @@ import com.ctrip.sqllin.dsl.sql.statement.JoinSelectStatement import com.ctrip.sqllin.dsl.sql.statement.WhereSelectStatement /** - * SQL 'GROUP BY' clause by select statement - * @author yaqiao + * GROUP BY clause for grouping rows in SELECT queries with aggregate functions. + * + * Generates SQL in the format: ` GROUP BY column1, column2, ...` + * + * Used with aggregate functions (COUNT, SUM, AVG, etc.) to group results: + * ```kotlin + * SELECT(user) GROUP_BY (user.department) HAVING (COUNT(user.id) GT 5) + * ``` + * + * @param T The entity type this clause operates on + * + * @author Yuang Qiao */ - public class GroupByClause internal constructor(private val columnNames: Iterable) : SelectClause { override val clauseStr: String @@ -41,6 +50,9 @@ public class GroupByClause internal constructor(private val columnNames: Iter } } +/** + * Creates a GROUP BY clause for aggregating rows. + */ @StatementDslMaker public fun GROUP_BY(vararg elements: ClauseElement): GroupByClause = GroupByClause(elements.toList()) diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/HavingClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/HavingClause.kt index 1ae5434..2cd0c31 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/HavingClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/HavingClause.kt @@ -21,10 +21,21 @@ import com.ctrip.sqllin.dsl.sql.statement.GroupBySelectStatement import com.ctrip.sqllin.dsl.sql.statement.HavingSelectStatement /** - * SQL 'HAVING' clause by select statement - * @author yaqiao + * HAVING clause for filtering grouped rows in SELECT queries. + * + * Similar to WHERE but operates on aggregated data after GROUP BY. Generates SQL in the format: + * ` HAVING condition` + * + * Used to filter groups based on aggregate function results: + * ```kotlin + * SELECT(user) GROUP_BY (user.department) HAVING (COUNT(user.id) GT 5) + * // Returns only departments with more than 5 users + * ``` + * + * @param T The entity type this clause operates on + * + * @author Yuang Qiao */ - internal class HavingClause(val selectCondition: SelectCondition) : ConditionClause(selectCondition) { override val clauseName: String = "HAVING" diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/InnerJoinClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/InnerJoinClause.kt index dfa0bb6..78bc8eb 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/InnerJoinClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/InnerJoinClause.kt @@ -20,10 +20,15 @@ import com.ctrip.sqllin.dsl.annotation.StatementDslMaker import com.ctrip.sqllin.dsl.sql.Table /** - * SQL "INNER JOIN" clause - * @author yaqiao + * INNER JOIN clause - returns rows where there's a match in both tables. + * + * Generates SQL: ` JOIN table` + * Requires ON or USING condition. + * + * @param R The result entity type after JOIN + * + * @author Yuang Qiao */ - internal class InnerJoinClause( vararg tables: Table<*>, ) : JoinClause(*tables) { @@ -31,12 +36,34 @@ internal class InnerJoinClause( override val clauseName: String = " JOIN " } +/** + * Creates an INNER JOIN clause (requires ON or USING). + * + * Usage: + * ```kotlin + * SELECT(user) JOIN (order) ON (user.id EQ order.userId) + * SELECT(user) JOIN (order) USING (user.id) + * ``` + */ @StatementDslMaker public fun JOIN(vararg tables: Table<*>): JoinClause = InnerJoinClause(*tables) +/** + * Alias for [JOIN] - creates an INNER JOIN clause. + */ @StatementDslMaker public inline fun INNER_JOIN(vararg tables: Table<*>): JoinClause = JOIN(*tables) +/** + * NATURAL INNER JOIN - automatically joins on columns with matching names. + * + * Generates SQL: ` NATURAL JOIN table` + * Does not require ON or USING. + * + * @param R The result entity type after JOIN + * + * @author Yuang Qiao + */ internal class NaturalInnerJoinClause( vararg tables: Table<*>, ) : NaturalJoinClause(*tables) { @@ -44,8 +71,19 @@ internal class NaturalInnerJoinClause( override val clauseName: String = " NATURAL JOIN " } +/** + * Creates a NATURAL JOIN clause (no ON/USING needed). + * + * Usage: + * ```kotlin + * SELECT(user) NATURAL_JOIN (profile) // Joins on matching column names + * ``` + */ @StatementDslMaker public fun NATURAL_JOIN(vararg tables: Table<*>): NaturalJoinClause = NaturalInnerJoinClause(*tables) +/** + * Alias for [NATURAL_JOIN]. + */ @StatementDslMaker public inline fun NATURAL_INNER_JOIN(vararg tables: Table<*>): NaturalJoinClause = NATURAL_JOIN(*tables) \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/LeftOuterJoinClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/LeftOuterJoinClause.kt index 7fe80af..9ba0235 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/LeftOuterJoinClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/LeftOuterJoinClause.kt @@ -20,10 +20,16 @@ import com.ctrip.sqllin.dsl.annotation.StatementDslMaker import com.ctrip.sqllin.dsl.sql.Table /** - * SQL "LEFT OUTER JOIN" clause - * @author yaqiao + * LEFT OUTER JOIN clause - returns all rows from the left table and matching rows from the right. + * + * Generates SQL: ` LEFT OUTER JOIN table` + * Requires ON or USING condition. + * Returns NULL for right table columns when there's no match. + * + * @param R The result entity type after JOIN + * + * @author Yuang Qiao */ - internal class LeftOuterJoinClause( vararg tables: Table<*> ) : JoinClause(*tables) { @@ -31,9 +37,29 @@ internal class LeftOuterJoinClause( override val clauseName: String = " LEFT OUTER JOIN " } +/** + * Creates a LEFT OUTER JOIN clause (requires ON or USING). + * + * Usage: + * ```kotlin + * SELECT(user) LEFT_OUTER_JOIN (order) ON (user.id EQ order.userId) + * // Returns all users, including those without orders + * ``` + */ @StatementDslMaker public fun LEFT_OUTER_JOIN(vararg tables: Table<*>): JoinClause = LeftOuterJoinClause(*tables) +/** + * NATURAL LEFT OUTER JOIN - automatically joins on matching column names. + * + * Generates SQL: ` NATURAL LEFT OUTER JOIN table` + * Does not require ON or USING. + * Returns all left table rows, with NULLs for non-matching right table columns. + * + * @param R The result entity type after JOIN + * + * @author Yuang Qiao + */ internal class NaturalLeftOuterJoinClause( vararg tables: Table<*> ) : NaturalJoinClause(*tables) { @@ -41,5 +67,13 @@ internal class NaturalLeftOuterJoinClause( override val clauseName: String = " NATURAL LEFT OUTER JOIN " } +/** + * Creates a NATURAL LEFT OUTER JOIN clause (no ON/USING needed). + * + * Usage: + * ```kotlin + * SELECT(user) NATURAL_LEFT_OUTER_JOIN (profile) + * ``` + */ @StatementDslMaker public fun NATURAL_LEFT_OUTER_JOIN(vararg tables: Table<*>): NaturalJoinClause = NaturalLeftOuterJoinClause(*tables) \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/LimitClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/LimitClause.kt index 9f15aae..6ab9df0 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/LimitClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/LimitClause.kt @@ -20,10 +20,19 @@ import com.ctrip.sqllin.dsl.annotation.StatementDslMaker import com.ctrip.sqllin.dsl.sql.statement.* /** - * SQL 'LIMIT' clause by select statement - * @author yaqiao + * LIMIT clause for restricting the number of rows returned by a SELECT query. + * + * Generates SQL in the format: ` LIMIT count` + * + * Often combined with OFFSET for pagination: + * ```kotlin + * SELECT(user) ORDER_BY (user.id to ASC) LIMIT 10 OFFSET 20 // Skip 20, take 10 + * ``` + * + * @param T The entity type this clause operates on + * + * @author Yuang Qiao */ - public class LimitClause internal constructor( private val count: Int, ) : SelectClause { @@ -31,6 +40,9 @@ public class LimitClause internal constructor( get() = " LIMIT $count" } +/** + * Creates a LIMIT clause to restrict result count. + */ @StatementDslMaker public fun LIMIT(count: Int): LimitClause = LimitClause(count) @@ -59,9 +71,19 @@ public infix fun JoinSelectStatement.LIMIT(count: Int): LimitSelectStatem } /** - * SQL 'OFFSET' clause by select statement + * OFFSET clause for skipping rows in a SELECT query (pagination). + * + * Generates SQL in the format: ` OFFSET rowNo` + * + * Must follow a LIMIT clause. Used for pagination: + * ```kotlin + * SELECT(user) LIMIT 10 OFFSET 20 // Skip first 20 rows, return next 10 + * ``` + * + * @param T The entity type this clause operates on + * + * @author Yuang Qiao */ - public class OffsetClause internal constructor( private val rowNo: Int, ) : SelectClause { diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/OrderByClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/OrderByClause.kt index 901267f..c66c263 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/OrderByClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/OrderByClause.kt @@ -21,10 +21,18 @@ import com.ctrip.sqllin.dsl.annotation.StatementDslMaker import com.ctrip.sqllin.dsl.sql.statement.* /** - * SQL 'order by' clause by select statement - * @author yaqiao + * ORDER BY clause for sorting SELECT query results. + * + * Generates SQL in the format: ` ORDER BY column1 ASC, column2 DESC, ...` + * + * Supports two modes: + * - Explicit direction: `ORDER_BY(user.name to ASC, user.age to DESC)` + * - Default ascending: `ORDER_BY(user.name, user.age)` + * + * @param T The entity type this clause operates on + * + * @author Yuang Qiao */ - public sealed interface OrderByClause : SelectClause internal class CompleteOrderByClause(private val column2WayMap: Map) : OrderByClause { diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SelectClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SelectClause.kt index e252b32..f2d6f1a 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SelectClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SelectClause.kt @@ -17,10 +17,25 @@ package com.ctrip.sqllin.dsl.sql.clause /** - * The SQL clause that could used for 'select' statement - * @author yaqiao + * Base interface for clauses used in SELECT statements. + * + * Represents SQL clauses that can be appended to SELECT queries to filter, order, group, + * limit, or join data. Each implementation provides its SQL string representation. + * + * Implementations: + * - [WhereClause]: WHERE condition filtering + * - [OrderByClause]: ORDER BY sorting + * - [LimitClause]: LIMIT row count restriction + * - [OffsetClause]: OFFSET row skipping + * - [GroupByClause]: GROUP BY aggregation grouping + * - [HavingClause]: HAVING condition for grouped data + * - [JoinClause], [InnerJoinClause], [LeftOuterJoinClause], [CrossJoinClause], [NaturalJoinClause]: JOIN operations + * + * @param T The entity type this clause operates on + * @property clauseStr The SQL string representation (e.g., " WHERE id = ?", " ORDER BY name ASC") + * + * @author Yuang Qiao */ - public sealed interface SelectClause : Clause { public val clauseStr: String } \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SelectCondition.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SelectCondition.kt index 49cd92e..f01af77 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SelectCondition.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SelectCondition.kt @@ -17,19 +17,39 @@ package com.ctrip.sqllin.dsl.sql.clause /** - * Present the single condition in where clause - * @author yaqiao + * Represents a condition expression used in WHERE or HAVING clauses. + * + * Encapsulates a single condition (e.g., `id = ?`, `age > 18`) along with its parameterized + * values. Supports combining conditions with AND/OR operators to build complex predicates. + * + * Conditions are built by comparison operations on [ClauseElement] instances: + * ```kotlin + * userTable.id EQ 42 // Creates: SelectCondition("id = ?", ["42"]) + * userTable.age GT 18 // Creates: SelectCondition("age > ?", ["18"]) + * ``` + * + * @property conditionSQL The SQL condition expression (may contain ? placeholders) + * @property parameters Parameterized query values (strings only), or null if none + * + * @author Yuang Qiao */ - public class SelectCondition internal constructor( internal val conditionSQL: String, internal val parameters: MutableList?, ) { - // Where condition 'OR' operator. + /** + * Combines this condition with another using OR. + * + * Creates: `(condition1) OR (condition2)` + */ internal infix fun or(next: SelectCondition): SelectCondition = append("OR", next) - // Where condition 'AND' operator. + /** + * Combines this condition with another using AND. + * + * Creates: `(condition1) AND (condition2)` + */ internal infix fun and(next: SelectCondition): SelectCondition = append("AND", next) private fun append(symbol: String, next: SelectCondition): SelectCondition { diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SetClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SetClause.kt index b3c25b7..d0925d6 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SetClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SetClause.kt @@ -19,10 +19,22 @@ package com.ctrip.sqllin.dsl.sql.clause import com.ctrip.sqllin.dsl.annotation.StatementDslMaker /** - * Present the single prediction in set clause - * @author yaqiao + * SET clause for UPDATE statements. + * + * Builds column assignments in the format: `column1 = ?, column2 = ?, ...` + * + * Used in UPDATE operations to specify new values: + * ```kotlin + * UPDATE(user) SET { + * it.name = "John" + * it.age = 30 + * } WHERE (user.id EQ 42) + * ``` + * + * @param T The entity type being updated + * + * @author Yuang Qiao */ - public class SetClause : Clause { private val clauseBuilder = StringBuilder() diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/WhereClause.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/WhereClause.kt index f507b30..8f2c6ec 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/WhereClause.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/WhereClause.kt @@ -23,10 +23,14 @@ import com.ctrip.sqllin.dsl.sql.statement.UpdateStatementWithoutWhereClause import com.ctrip.sqllin.dsl.sql.statement.WhereSelectStatement /** - * SQL "WHERE" clause - * @author yaqiao + * WHERE clause for filtering rows in SELECT statements or targeting rows in UPDATE/DELETE statements. + * + * Wraps a [SelectCondition] and generates SQL in the format: ` WHERE condition` + * + * @param T The entity type this clause operates on + * + * @author Yuang Qiao */ - public class WhereClause internal constructor( internal val selectCondition: SelectCondition, ) : ConditionClause(selectCondition) { @@ -34,6 +38,16 @@ public class WhereClause internal constructor( override val clauseName: String = "WHERE" } +/** + * Creates a WHERE clause for use in DSL operations. + * + * Usage: + * ```kotlin + * SELECT(user) WHERE (user.id EQ 42) + * UPDATE(user) SET { it.name = "John" } WHERE (user.id EQ 42) + * DELETE(user) WHERE (user.age GT 18) + * ``` + */ @StatementDslMaker public fun WHERE(condition: SelectCondition): WhereClause = WhereClause(condition) diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt index 41c6b95..4c696e7 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt @@ -23,23 +23,46 @@ import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule /** - * Abstract Encode the object to UPDATE statement - * @author yaqiao + * Base encoder for converting Kotlin objects to SQL VALUES clauses using kotlinx.serialization. + * + * This abstract class leverages kotlinx.serialization's encoder API to traverse entity objects + * and generate SQL parameter placeholders (for strings) or inline values (for numbers/booleans). + * String values are replaced with `?` placeholders and collected in [parameters] for safe + * parameterized queries. + * + * Subclasses must implement [appendTail] to control punctuation between values (e.g., commas, + * parentheses) depending on the SQL statement type. + * + * @author Yuang Qiao */ - @OptIn(ExperimentalSerializationApi::class) internal abstract class AbstractValuesEncoder : AbstractEncoder() { final override val serializersModule: SerializersModule = EmptySerializersModule() + /** + * StringBuilder accumulating the SQL VALUES clause. + */ protected abstract val sqlStrBuilder: StringBuilder + + /** + * List collecting string parameter values for parameterized queries. + */ abstract val parameters: MutableList + /** + * Appends appropriate punctuation after each encoded value. + * + * Implementations determine whether to append commas, closing parentheses, etc. + */ protected abstract fun StringBuilder.appendTail(): StringBuilder protected var elementsIndex = 0 protected var elementsCount = 0 + /** + * The complete SQL VALUES clause generated by this encoder. + */ val valuesSQL get() = sqlStrBuilder.toString() @@ -49,6 +72,9 @@ internal abstract class AbstractValuesEncoder : AbstractEncoder() { return true } + /** + * Encodes Boolean as SQLite integer (1 for true, 0 for false). + */ override fun encodeBoolean(value: Boolean) = encodeByte(if (value) 1 else 0) override fun encodeByte(value: Byte) { @@ -67,8 +93,17 @@ internal abstract class AbstractValuesEncoder : AbstractEncoder() { sqlStrBuilder.append(value).appendTail() } + /** + * Encodes Char as a string. + */ override fun encodeChar(value: Char) = encodeString(value.toString()) + /** + * Encodes String as a parameterized placeholder. + * + * Appends `?` to the SQL and adds the actual value to [parameters] + * for safe parameterized query execution. + */ override fun encodeString(value: String) { sqlStrBuilder.append('?').appendTail() parameters.add(value) @@ -86,5 +121,8 @@ internal abstract class AbstractValuesEncoder : AbstractEncoder() { sqlStrBuilder.append("NULL").appendTail() } + /** + * Encodes enum as its ordinal integer value. + */ override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = encodeInt(index) } \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/EncodeEntities2SQL.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/EncodeEntities2SQL.kt index 4fd7475..2543967 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/EncodeEntities2SQL.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/EncodeEntities2SQL.kt @@ -20,10 +20,29 @@ import com.ctrip.sqllin.dsl.sql.Table import kotlinx.serialization.descriptors.SerialDescriptor /** - * Some function that used for encode entities to SQL + * Utility functions for encoding entity objects to SQL statements. + * * @author Yuang Qiao */ +/** + * Encodes a collection of entities into an INSERT statement's VALUES clause. + * + * Generates SQL in the format: + * ``` + * (column1, column2, ...) VALUES (?, ?, ...), (?, ?, ...), ... + * ``` + * + * Handles primary key logic: + * - For auto-increment `Long?` primary keys, omits the ID column unless [isInsertWithId] is true + * - For user-provided primary keys or composite keys, includes all columns + * + * @param table The table definition containing serialization and primary key metadata + * @param builder StringBuilder to append the SQL to + * @param values The entities to insert + * @param parameters Mutable list to collect parameterized query values + * @param isInsertWithId Whether to include the primary key column for rowid-backed keys + */ internal fun encodeEntities2InsertValues( table: Table, builder: StringBuilder, @@ -59,6 +78,14 @@ internal fun encodeEntities2InsertValues( } } +/** + * Appends database column names to the StringBuilder, optionally excluding a primary key. + * + * @param descriptor The serialization descriptor containing column/element names + * @param primaryKeyName The name of the primary key column to potentially exclude + * @param isInsertId Whether to include the primary key column + * @return The index of the excluded primary key column, or -1 if all columns were included + */ internal fun StringBuilder.appendDBColumnName( descriptor: SerialDescriptor, primaryKeyName: String?, @@ -86,6 +113,9 @@ internal fun StringBuilder.appendDBColumnName( index } +/** + * Appends all database column names from the descriptor as a comma-separated list. + */ internal infix fun StringBuilder.appendDBColumnName(descriptor: SerialDescriptor) { if (descriptor.elementsCount > 0) append(descriptor.getElementName(0)) diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/InsertValuesEncoder.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/InsertValuesEncoder.kt index 6d3d94d..31357ba 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/InsertValuesEncoder.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/InsertValuesEncoder.kt @@ -17,16 +17,26 @@ package com.ctrip.sqllin.dsl.sql.compiler /** - * Encode the object to INSERT SQL statement - * @author yaqiao + * Encoder for generating VALUES clauses in INSERT statements. + * + * Produces SQL in the format: `(value1, value2, ..., valueN)` + * + * Each entity is encoded into a parenthesized tuple of values, with commas separating + * elements within the tuple. + * + * Example output: `(123, 'Alice', 25)` or `(?, ?, ?)` with parameters `["Alice"]` + * + * @author Yuang Qiao */ - internal class InsertValuesEncoder( override val parameters: MutableList, ) : AbstractValuesEncoder() { override val sqlStrBuilder = StringBuilder("(") + /** + * Appends comma between values or closing parenthesis after the last value. + */ override fun StringBuilder.appendTail(): StringBuilder { val symbol = if (elementsIndex < elementsCount - 1) ',' diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt index 0b8af6f..3b05694 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt @@ -26,10 +26,20 @@ import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule /** - * Decoder the `CommonCursor` to object when SQLite query - * @author yaqiao + * Decoder for converting SQLite query results to Kotlin objects using kotlinx.serialization. + * + * This decoder reads data from a [CommonCursor] (representing a SQLite result set) and + * deserializes it into strongly-typed entity objects. It maps cursor columns to object + * properties by matching column names with serialization descriptor element names. + * + * The decoder handles: + * - Type conversions from SQLite types to Kotlin types + * - Null value handling for nullable properties + * - Boolean mapping (SQLite integers to Kotlin booleans) + * - Enum deserialization (ordinal values to enum instances) + * + * @author Yuang Qiao */ - @OptIn(ExperimentalSerializationApi::class) internal class QueryDecoder( private val cursor: CommonCursor @@ -41,6 +51,11 @@ internal class QueryDecoder( override val serializersModule: SerializersModule = EmptySerializersModule() + /** + * Determines the next property to decode from the descriptor. + * + * Skips properties that don't have corresponding columns in the cursor. + */ override tailrec fun decodeElementIndex(descriptor: SerialDescriptor): Int = if (elementIndex == descriptor.elementsCount) CompositeDecoder.DECODE_DONE @@ -56,23 +71,41 @@ internal class QueryDecoder( override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = QueryDecoder(cursor) + /** + * Resolves the cursor column index for the current element name. + */ private inline val cursorColumnIndex get() = cursor.getColumnIndex(elementName) + /** + * Helper to safely deserialize a value from the cursor with column validation. + */ private inline fun deserialize(block: (Int) -> T): T = cursorColumnIndex.let { if (it >= 0) block(it) else throw SerializationException("The Cursor doesn't have this column") } + /** + * Decodes SQLite integer (1/0) to Boolean (true/false). + */ override fun decodeBoolean(): Boolean = deserialize { cursor.getInt(it) > 0 } override fun decodeByte(): Byte = deserialize { cursor.getInt(it).toByte() } override fun decodeShort(): Short = deserialize { cursor.getInt(it).toShort() } override fun decodeInt(): Int = deserialize { cursor.getInt(it) } override fun decodeLong(): Long = deserialize { cursor.getLong(it) } + /** + * Decodes first character of string, or null character if string is null. + */ override fun decodeChar(): Char = deserialize { cursor.getString(it)?.first() ?: '\u0000' } override fun decodeString(): String = deserialize { cursor.getString(it) ?: "" } override fun decodeFloat(): Float = deserialize { cursor.getFloat(it) } override fun decodeDouble(): Double = deserialize { cursor.getDouble(it) } + /** + * Decodes enum by its ordinal value stored as an integer. + */ override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = deserialize { cursor.getInt(it) } + /** + * Determines if the current column contains a non-null value. + */ override fun decodeNotNullMark(): Boolean = !cursor.isNull(cursorColumnIndex) || !elementNullable } \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Create.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Create.kt index 12a6920..256bad7 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Create.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Create.kt @@ -21,18 +21,50 @@ import com.ctrip.sqllin.dsl.sql.Table import com.ctrip.sqllin.dsl.sql.statement.CreateStatement /** - * SQL create + * CREATE TABLE operation builder. + * + * Constructs CREATE TABLE statements by inspecting entity serialization descriptors and + * generating appropriate SQLite column definitions with type mappings, nullability constraints, + * and primary key declarations. + * * @author Yuang Qiao */ - internal object Create : Operation { override val sqlStr: String get() = "CREATE TABLE " + /** + * Builds a CREATE TABLE statement for the given table definition. + * + * @param table Table definition containing entity metadata + * @param connection Database connection for execution + * @return CREATE statement ready for execution + */ fun create(table: Table, connection: DatabaseConnection): CreateStatement = CreateStatement(buildSQL(table), connection) + /** + * Generates the CREATE TABLE SQL by inspecting entity properties. + * + * Maps Kotlin types to SQLite types: + * - Byte/UByte → TINYINT + * - Short/UShort → SMALLINT + * - Int/UInt → INT + * - Long → INTEGER (for primary keys with AUTOINCREMENT) or BIGINT + * - ULong → BIGINT + * - Float → FLOAT + * - Double → DOUBLE + * - Boolean → BOOLEAN + * - Char → CHAR(1) + * - String → TEXT + * - ByteArray → BLOB + * + * Handles: + * - Nullable properties (omit NOT NULL constraint) + * - Single primary keys (PRIMARY KEY, optionally AUTOINCREMENT) + * - Composite primary keys (PRIMARY KEY clause at end) + */ private fun buildSQL(table: Table): String = buildString { append(sqlStr) append(table.tableName) diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Delete.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Delete.kt index a921dc3..5e17e46 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Delete.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Delete.kt @@ -23,15 +23,28 @@ import com.ctrip.sqllin.dsl.sql.clause.WhereClause import com.ctrip.sqllin.dsl.sql.statement.UpdateDeleteStatement /** - * SQL delete - * @author yaqiao + * DELETE operation builder. + * + * Constructs DELETE statements with optional WHERE clauses. Supports both targeted deletion + * (with WHERE) and bulk deletion (all rows). + * + * @author Yuang Qiao */ - internal object Delete : Operation { override val sqlStr: String get() = "DELETE FROM " + /** + * Builds a DELETE statement with WHERE clause. + * + * Generates SQL in the format: `DELETE FROM table WHERE condition` + * + * @param table Table to delete from + * @param connection Database connection for execution + * @param whereClause WHERE condition specifying which rows to delete + * @return Final DELETE statement ready for execution + */ fun delete(table: Table<*>, connection: DatabaseConnection, whereClause: WhereClause): SingleStatement { val sql = buildString { buildBaseDeleteStatement(table) @@ -40,6 +53,15 @@ internal object Delete : Operation { return UpdateDeleteStatement(sql, connection, whereClause.selectCondition.parameters) } + /** + * Builds a DELETE statement without WHERE clause (deletes all rows). + * + * Generates SQL in the format: `DELETE FROM table` + * + * @param table Table to delete all rows from + * @param connection Database connection for execution + * @return Final DELETE statement ready for execution + */ fun deleteAllEntities(table: Table<*>, connection: DatabaseConnection): SingleStatement { val sql = buildString { buildBaseDeleteStatement(table) diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/FullNameCache.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/FullNameCache.kt index 7a65088..45b4270 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/FullNameCache.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/FullNameCache.kt @@ -17,10 +17,21 @@ package com.ctrip.sqllin.dsl.sql.operation /** - * Cache for primitive types' qualified name + * Cached qualified names for Kotlin types used in SQLite type mapping. + * + * Provides pre-computed fully qualified names for performance during CREATE TABLE generation. + * These names are matched against kotlinx.serialization descriptor serial names to determine + * the appropriate SQLite column type. + * + * Used by [Create.buildSQL] to map Kotlin types to SQLite types: + * - Integer types → TINYINT, SMALLINT, INT, BIGINT, INTEGER + * - Floating-point types → FLOAT, DOUBLE + * - Boolean → BOOLEAN + * - Character/String → CHAR(1), TEXT + * - ByteArray → BLOB + * * @author Yuang Qiao */ - internal object FullNameCache { val BYTE = Byte::class.qualifiedName!! diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Insert.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Insert.kt index f2377a4..2b94242 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Insert.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Insert.kt @@ -23,15 +23,32 @@ import com.ctrip.sqllin.dsl.sql.Table import com.ctrip.sqllin.dsl.sql.compiler.encodeEntities2InsertValues /** - * SQL insert + * INSERT operation builder. + * + * Constructs INSERT statements by encoding entity objects into SQL VALUES clauses with + * parameterized queries. Supports both auto-generated primary keys and user-provided keys. + * * @author Yuang Qiao */ - internal object Insert : Operation { override val sqlStr: String get() = "INSERT INTO " + /** + * Builds an INSERT statement for the given entities. + * + * Generates SQL in the format: + * ``` + * INSERT INTO table_name (column1, column2, ...) VALUES (?, ?, ...), (?, ?, ...), ... + * ``` + * + * @param table Table definition containing serialization metadata + * @param connection Database connection for execution + * @param entities Entities to insert + * @param isInsertWithId Whether to include the primary key column for auto-increment keys + * @return INSERT statement ready for execution + */ fun insert(table: Table, connection: DatabaseConnection, entities: Iterable, isInsertWithId: Boolean = false): SingleStatement { val parameters = ArrayList() val sql = buildString { diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Operation.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Operation.kt index 0cd4695..58fa385 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Operation.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Operation.kt @@ -17,10 +17,22 @@ package com.ctrip.sqllin.dsl.sql.operation /** - * SQL operation: SELECT, UPDATE, DELETE, INSERT - * @author yaqiao + * Base interface for SQL operations in the DSL. + * + * Marker interface for operation builders (SELECT, UPDATE, DELETE, INSERT, CREATE) that construct + * SQL strings. Each operation implementation accumulates clauses and generates the final SQL. + * + * Implementations: + * - [SelectBuilder]: SELECT queries + * - [UpdateBuilder]: UPDATE statements + * - [DeleteBuilder]: DELETE statements + * - [InsertBuilder]: INSERT statements + * - [CreateBuilder]: CREATE TABLE statements + * + * @property sqlStr The accumulated SQL string built by this operation + * + * @author Yuang Qiao */ - internal interface Operation { val sqlStr: String } \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Select.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Select.kt index 69e37e8..feecfab 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Select.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Select.kt @@ -24,15 +24,24 @@ import com.ctrip.sqllin.dsl.sql.statement.* import kotlinx.serialization.DeserializationStrategy /** - * SQL query - * @author yaqiao + * SELECT operation builder. + * + * Constructs SELECT statements by combining table information with clauses (WHERE, ORDER BY, + * LIMIT, GROUP BY, JOIN). Creates the appropriate statement type based on which clauses are + * initially provided, enforcing compile-time clause ordering through the statement hierarchy. + * + * @author Yuang Qiao */ - internal object Select : Operation { override val sqlStr: String get() = "SELECT " + /** + * Builds a SELECT statement with WHERE clause. + * + * @return Statement that can be followed by GROUP BY, ORDER BY, or LIMIT + */ fun select( table: Table, clause: WhereClause, @@ -43,6 +52,11 @@ internal object Select : Operation { ): WhereSelectStatement = WhereSelectStatement(buildSQL(table, clause, isDistinct, deserializer), deserializer, connection, container, clause.selectCondition.parameters) + /** + * Builds a SELECT statement with ORDER BY clause. + * + * @return Statement that can be followed by LIMIT + */ fun select( table: Table, clause: OrderByClause, @@ -53,6 +67,11 @@ internal object Select : Operation { ): OrderBySelectStatement = OrderBySelectStatement(buildSQL(table, clause, isDistinct, deserializer), deserializer, connection, container, null) + /** + * Builds a SELECT statement with LIMIT clause. + * + * @return Statement that can be followed by OFFSET + */ fun select( table: Table, clause: LimitClause, @@ -63,6 +82,11 @@ internal object Select : Operation { ): LimitSelectStatement = LimitSelectStatement(buildSQL(table, clause, isDistinct, deserializer), deserializer, connection, container, null) + /** + * Builds a SELECT statement with GROUP BY clause. + * + * @return Statement that can be followed by HAVING or ORDER BY + */ fun select( table: Table, clause: GroupByClause, @@ -73,6 +97,13 @@ internal object Select : Operation { ): GroupBySelectStatement = GroupBySelectStatement(buildSQL(table, clause, isDistinct, deserializer), deserializer, connection, container, null) + /** + * Builds a SELECT statement with NATURAL JOIN clause. + * + * Natural joins automatically match columns with the same name in both tables. + * + * @return Statement that can be followed by WHERE, GROUP BY, ORDER BY, or LIMIT + */ fun select( table: Table<*>, clause: NaturalJoinClause, @@ -83,6 +114,13 @@ internal object Select : Operation { ) : JoinSelectStatement = JoinSelectStatement(buildSQL(table, clause, isDistinct, deserializer), deserializer, connection, container, null) + /** + * Builds a SELECT statement with JOIN clause (requires ON or USING). + * + * Returns an intermediate state that must be completed with either an ON or USING clause. + * + * @return Incomplete JOIN statement requiring condition + */ fun select( table: Table<*>, clause: JoinClause, @@ -115,6 +153,13 @@ internal object Select : Operation { append(clause.clauseStr) } + /** + * Builds a simple SELECT statement without any clauses. + * + * Generates SQL in the format: `SELECT columns FROM table` + * + * @return Final SELECT statement ready for execution + */ fun select( table: Table, isDistinct: Boolean, diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Update.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Update.kt index 7013327..1084446 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Update.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Update.kt @@ -23,15 +23,30 @@ import com.ctrip.sqllin.dsl.sql.clause.SetClause import com.ctrip.sqllin.dsl.sql.statement.UpdateStatementWithoutWhereClause /** - * SQL update - * @author yaqiao + * UPDATE operation builder. + * + * Constructs UPDATE statements by combining table information with SET clauses. Returns an + * intermediate statement that can either execute immediately (update all rows) or be refined + * with a WHERE clause (update filtered rows). + * + * @author Yuang Qiao */ - internal object Update : Operation { override val sqlStr: String get() = "UPDATE " + /** + * Builds an UPDATE statement with SET clause. + * + * Generates SQL in the format: `UPDATE table SET column1 = ?, column2 = ?, ...` + * + * @param table Table definition + * @param connection Database connection for execution + * @param container Statement container for DSL scope management + * @param clause SET clause with column assignments + * @return Statement that can either execute or be refined with WHERE + */ fun update( table: Table, connection: DatabaseConnection, diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/DatabaseExecuteEngine.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/DatabaseExecuteEngine.kt index 3eda9eb..b8ac085 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/DatabaseExecuteEngine.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/DatabaseExecuteEngine.kt @@ -17,10 +17,16 @@ package com.ctrip.sqllin.dsl.sql.statement /** - * Collect and execute all SQL statement in 'database {}' block - * @author yaqiao + * Execution engine for top-level database DSL operations. + * + * Collects all statements created within a `Database` scope and executes them when the scope + * exits. Handles both individual statements and transaction groups. Supports progressive clause + * building by allowing UPDATE and SELECT statements to be replaced with refined versions. + * + * @property enableSimpleSQLLog Whether to print SQL and parameters before execution + * + * @author Yuang Qiao */ - internal class DatabaseExecuteEngine( private val enableSimpleSQLLog: Boolean, ) : StatementContainer { diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/ExecutableStatement.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/ExecutableStatement.kt index a4d3b9d..7703b1f 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/ExecutableStatement.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/ExecutableStatement.kt @@ -17,10 +17,23 @@ package com.ctrip.sqllin.dsl.sql.statement /** - * Abstract SQL statement that could execute - * @author yaqiao + * Base interface for SQL statements that can be executed against the database. + * + * Implementations include: + * - [SingleStatement]: Individual SQL operations (INSERT, UPDATE, DELETE, CREATE, SELECT) + * - [TransactionStatementsGroup]: Multiple statements wrapped in a transaction + * + * Statements are collected during DSL building and executed when the [com.ctrip.sqllin.dsl.DatabaseScope] + * exits. + * + * @author Yuang Qiao */ - public sealed interface ExecutableStatement { + /** + * Executes this statement against the database. + * + * For single statements, this runs the SQL directly. + * For statement groups, this executes all contained statements in order. + */ public fun execute() } \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/JoinStatementWithoutCondition.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/JoinStatementWithoutCondition.kt index 18630e9..2e7e456 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/JoinStatementWithoutCondition.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/JoinStatementWithoutCondition.kt @@ -22,10 +22,20 @@ import com.ctrip.sqllin.dsl.sql.clause.SelectCondition import kotlinx.serialization.DeserializationStrategy /** - * SQL 'JOIN' statement, but need add 'ON' or 'USING' statement - * @author yaqiao + * Intermediate JOIN statement requiring an ON or USING condition. + * + * Represents a JOIN operation that has been initiated but not yet completed. In SQL, a JOIN + * must specify how tables relate through either: + * - USING clause: Lists common column names to join on + * - ON clause: Specifies a join condition expression + * + * This class enforces the requirement at compile time by not extending [SelectStatement]. + * It converts to [JoinSelectStatement] only after a condition is added. + * + * @param R The result entity type after JOIN + * + * @author Yuang Qiao */ - public class JoinStatementWithoutCondition internal constructor( private val sqlStr: String, private val deserializer: DeserializationStrategy, @@ -33,6 +43,15 @@ public class JoinStatementWithoutCondition internal constructor( private val container: StatementContainer, private val addSelectStatement: (SelectStatement) -> Unit ) { + /** + * Completes the JOIN by adding a USING clause. + * + * Generates SQL in the format: `JOIN table USING (column1, column2, ...)`. + * The USING clause specifies columns that exist in both tables with the same name. + * + * @param clauseElements Column elements to join on (must not be empty) + * @return Completed JOIN statement that can accept further clauses + */ internal infix fun convertToJoinSelectStatement(clauseElements: Iterable): JoinSelectStatement { val iterator = clauseElements.iterator() require(iterator.hasNext()) { "Param 'clauseElements' must not be empty!!!" } @@ -51,6 +70,15 @@ public class JoinStatementWithoutCondition internal constructor( return joinStatement } + /** + * Completes the JOIN by adding an ON clause. + * + * Generates SQL in the format: `JOIN table ON condition`. + * The ON clause specifies an arbitrary boolean expression for joining tables. + * + * @param condition Join condition (e.g., table1.id = table2.foreign_id) + * @return Completed JOIN statement that can accept further clauses + */ internal infix fun convertToJoinSelectStatement(condition: SelectCondition): JoinSelectStatement { val sql = buildString { append(sqlStr) diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/OtherStatement.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/OtherStatement.kt index abdc811..2514585 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/OtherStatement.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/OtherStatement.kt @@ -19,10 +19,22 @@ package com.ctrip.sqllin.dsl.sql.statement import com.ctrip.sqllin.driver.DatabaseConnection /** - * Update statement without 'WHERE' clause, that could execute or link 'WHERE' clause + * UPDATE statement without WHERE clause. + * + * Represents an UPDATE operation that can either: + * - Execute immediately (updates all rows in the table) + * - Be refined by adding a WHERE clause to target specific rows + * + * This intermediate state enables the DSL to support both: + * ```kotlin + * UPDATE(user) SET { /* ... */ } // Updates all rows + * UPDATE(user) SET { /* ... */ } WHERE { /* ... */ } // Updates filtered rows + * ``` + * + * @param T The entity type being updated + * * @author Yuang Qiao */ - public class UpdateStatementWithoutWhereClause internal constructor( preSQLStr: String, internal val statementContainer: StatementContainer, @@ -32,6 +44,14 @@ public class UpdateStatementWithoutWhereClause internal constructor( public override fun execute(): Unit = connection.executeUpdateDelete(sqlStr, params) } +/** + * UPDATE or DELETE statement with WHERE clause applied (final form). + * + * Represents a complete UPDATE or DELETE operation ready for execution. The WHERE clause + * has already been applied, so the statement targets specific rows. + * + * @author Yuang Qiao + */ public class UpdateDeleteStatement internal constructor( sqlStr: String, private val connection: DatabaseConnection, @@ -40,6 +60,14 @@ public class UpdateDeleteStatement internal constructor( public override fun execute(): Unit = connection.executeUpdateDelete(sqlStr, params) } +/** + * INSERT statement (final form). + * + * Represents a complete INSERT operation with entities encoded as parameterized VALUES. + * Executes as a single batch insert for all entities. + * + * @author Yuang Qiao + */ public class InsertStatement internal constructor( sqlStr: String, private val connection: DatabaseConnection, @@ -48,6 +76,14 @@ public class InsertStatement internal constructor( public override fun execute(): Unit = connection.executeInsert(sqlStr, params) } +/** + * CREATE statement (final form). + * + * Represents a complete CREATE TABLE operation. Does not support parameterized queries + * since DDL statements use direct SQL execution. + * + * @author Yuang Qiao + */ public class CreateStatement internal constructor( sqlStr: String, private val connection: DatabaseConnection, diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/SelectStatement.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/SelectStatement.kt index 6d4a133..6d2439a 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/SelectStatement.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/SelectStatement.kt @@ -25,10 +25,23 @@ import kotlinx.serialization.DeserializationStrategy import kotlin.concurrent.Volatile /** - * Select statement + * Base class for SELECT statements with progressive clause building. + * + * Represents a SELECT query that can be executed to retrieve and deserialize entities from the database. + * The class hierarchy enforces SQL clause ordering at compile time - each subclass accepts only valid + * subsequent clauses (e.g., WHERE can be followed by GROUP BY, ORDER BY, or LIMIT). + * + * Results are lazily evaluated and cached after [execute] is called. Use [getResults] to retrieve + * the deserialized entities. + * + * @param T The entity type returned by this query + * @property deserializer kotlinx.serialization strategy for decoding cursor rows to entities + * @property connection Database connection for executing the query + * @property container Statement container for managing this statement in the DSL scope + * @property parameters Parameterized query values (strings only), or null if none + * * @author Yuang Qiao */ - public sealed class SelectStatement( sqlStr: String, internal val deserializer: DeserializationStrategy, @@ -47,6 +60,14 @@ public sealed class SelectStatement( cursor = connection.query(sqlStr, params) } + /** + * Retrieves the query results as a list of deserialized entities. + * + * Results are lazily computed on first call and cached for subsequent calls. + * Throws [IllegalStateException] if called before [execute]. + * + * @return List of entities matching the query + */ public fun getResults(): List = result ?: cursor?.use { val decoder = QueryDecoder(it) result = buildList { @@ -75,6 +96,16 @@ public sealed class SelectStatement( } } +/** + * SELECT statement with WHERE clause applied. + * + * Can be followed by: + * - GROUP BY + * - ORDER BY + * - LIMIT + * + * @author Yuang Qiao + */ public class WhereSelectStatement internal constructor( sqlStr: String, deserializer: DeserializationStrategy, @@ -93,6 +124,17 @@ public class WhereSelectStatement internal constructor( GroupBySelectStatement(buildSQL(clause), deserializer, connection, container, parameters) } +/** + * SELECT statement with JOIN clause applied. + * + * Can be followed by: + * - WHERE + * - GROUP BY + * - ORDER BY + * - LIMIT + * + * @author Yuang Qiao + */ public class JoinSelectStatement internal constructor( sqlStr: String, deserializer: DeserializationStrategy, @@ -121,6 +163,15 @@ public class JoinSelectStatement internal constructor( GroupBySelectStatement(buildSQL(clause), deserializer, connection, container, parameters) } +/** + * SELECT statement with GROUP BY clause applied. + * + * Can be followed by: + * - HAVING + * - ORDER BY + * + * @author Yuang Qiao + */ public class GroupBySelectStatement internal constructor( sqlStr: String, deserializer: DeserializationStrategy, @@ -143,6 +194,15 @@ public class GroupBySelectStatement internal constructor( } } +/** + * SELECT statement with HAVING clause applied. + * + * Can be followed by: + * - ORDER BY + * - LIMIT + * + * @author Yuang Qiao + */ public class HavingSelectStatement internal constructor( sqlStr: String, deserializer: DeserializationStrategy, @@ -158,6 +218,14 @@ public class HavingSelectStatement internal constructor( LimitSelectStatement(buildSQL(clause), deserializer, connection, container, parameters) } +/** + * SELECT statement with ORDER BY clause applied. + * + * Can be followed by: + * - LIMIT + * + * @author Yuang Qiao + */ public class OrderBySelectStatement internal constructor( sqlStr: String, deserializer: DeserializationStrategy, @@ -170,6 +238,14 @@ public class OrderBySelectStatement internal constructor( LimitSelectStatement(buildSQL(clause), deserializer, connection, container, parameters) } +/** + * SELECT statement with LIMIT clause applied. + * + * Can be followed by: + * - OFFSET + * + * @author Yuang Qiao + */ public class LimitSelectStatement internal constructor( sqlStr: String, deserializer: DeserializationStrategy, @@ -182,6 +258,14 @@ public class LimitSelectStatement internal constructor( FinalSelectStatement(buildSQL(clause), deserializer, connection, container, parameters) } +/** + * Final SELECT statement with all clauses applied. + * + * This is the terminal state in the SELECT statement hierarchy - no further clauses can be added. + * The statement is ready for execution. + * + * @author Yuang Qiao + */ public class FinalSelectStatement internal constructor( sqlStr: String, deserializer: DeserializationStrategy, diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/SingleStatement.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/SingleStatement.kt index 9e90463..813520f 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/SingleStatement.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/SingleStatement.kt @@ -17,19 +17,38 @@ package com.ctrip.sqllin.dsl.sql.statement /** - * Abstract Single executable statement - * @author yaqiao + * Base class for individual SQL statements. + * + * Represents a single SQL operation (INSERT, UPDATE, DELETE, CREATE, or SELECT) that can be + * executed against the database. Statements maintain their SQL string and optional parameters + * for parameterized queries. + * + * Subclasses include specific statement types defined in the [com.ctrip.sqllin.dsl.sql.operation] package. + * + * @property sqlStr The complete SQL string for this statement + * + * @author Yuang Qiao */ - public sealed class SingleStatement( public val sqlStr: String, ) : ExecutableStatement { + /** + * Parameters for parameterized query placeholders (strings only). + * + * `null` if the statement has no parameters. + */ internal abstract val parameters: MutableList? + /** + * Parameters converted to array format for driver execution. + */ internal val params: Array? get() = parameters?.toTypedArray() + /** + * Logs the SQL string and parameters for debugging. + */ internal fun printlnSQL() { print("SQL String: $sqlStr") parameters?.let { diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/StatementContainer.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/StatementContainer.kt index dc668b5..6cbd3cb 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/StatementContainer.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/StatementContainer.kt @@ -17,11 +17,25 @@ package com.ctrip.sqllin.dsl.sql.statement /** - * The container that used for store statement - * @author yaqiao + * Container for managing and modifying SQL statements. + * + * Used by statement builders (e.g., UPDATE, JOIN) to replace or update the last statement + * in a collection when DSL operations refine or extend it. For example, when an UPDATE + * statement adds a WHERE clause, it replaces the initial UPDATE statement. + * + * Implementations: + * - [DatabaseExecuteEngine]: Executes standalone statements + * - [TransactionStatementsGroup]: Groups statements within a transaction + * - [UnionSelectStatementGroup]: Accumulates SELECT statements for UNION operations + * + * @author Yuang Qiao */ - internal fun interface StatementContainer { + /** + * Replaces the most recently added statement with a modified version. + * + * Used when DSL operations progressively build up a statement (e.g., adding WHERE to UPDATE). + */ infix fun changeLastStatement(statement: SingleStatement) } \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/TransactionStatementsGroup.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/TransactionStatementsGroup.kt index 3d78389..1739a5c 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/TransactionStatementsGroup.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/TransactionStatementsGroup.kt @@ -20,10 +20,17 @@ import com.ctrip.sqllin.driver.DatabaseConnection import com.ctrip.sqllin.driver.withTransaction /** - * The group of some statements those them in same transaction - * @author yaqiao + * Container for statements executed within a single database transaction. + * + * Collects all SQL statements written inside a transaction DSL scope and executes them + * atomically. If any statement fails, the entire transaction rolls back. Supports progressive + * clause building on UPDATE and SELECT statements. + * + * @property databaseConnection Connection for executing the transactional operations + * @property enableSimpleSQLLog Whether to log each statement's SQL before execution + * + * @author Yuang Qiao */ - internal class TransactionStatementsGroup( private val databaseConnection: DatabaseConnection, private val enableSimpleSQLLog: Boolean, diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/UnionSelectStatementGroup.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/UnionSelectStatementGroup.kt index 5db43a1..d7da0c8 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/UnionSelectStatementGroup.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/UnionSelectStatementGroup.kt @@ -17,10 +17,16 @@ package com.ctrip.sqllin.dsl.sql.statement /** - * Used for compose multi select statement that use the 'UNION' clause - * @author yaqiao + * Container for building UNION queries from multiple SELECT statements. + * + * Accumulates SELECT statements written inside a UNION DSL scope and combines them + * with UNION or UNION ALL operators. Supports progressive clause building on individual + * SELECT statements before they are unioned. + * + * @param T The entity type returned by all SELECT statements (must be compatible) + * + * @author Yuang Qiao */ - internal class UnionSelectStatementGroup : StatementContainer { private val statementList = ArrayDeque>() @@ -29,6 +35,15 @@ internal class UnionSelectStatementGroup : StatementContainer { statementList.add(selectStatement) } + /** + * Combines all accumulated SELECT statements into a single UNION query. + * + * Merges parameters from all statements and joins their SQL with UNION or UNION ALL operators. + * Requires at least two SELECT statements. + * + * @param isUnionAll If true, uses UNION ALL (includes duplicates); otherwise uses UNION (distinct rows) + * @return Final SELECT statement representing the complete UNION query + */ internal fun unionStatements(isUnionAll: Boolean): FinalSelectStatement { require(statementList.isNotEmpty()) { "Please write at least two 'select' statements on 'UNION' scope" } var parameters: MutableList? = null diff --git a/sqllin-processor/src/main/kotlin/com/ctrip/sqllin/processor/ClauseProcessor.kt b/sqllin-processor/src/main/kotlin/com/ctrip/sqllin/processor/ClauseProcessor.kt index a9ffc0a..566285a 100644 --- a/sqllin-processor/src/main/kotlin/com/ctrip/sqllin/processor/ClauseProcessor.kt +++ b/sqllin-processor/src/main/kotlin/com/ctrip/sqllin/processor/ClauseProcessor.kt @@ -26,14 +26,28 @@ import com.google.devtools.ksp.validate import java.io.OutputStreamWriter /** - * Generate the clause properties for data classes that present the database entity + * KSP symbol processor that generates table objects for database entities. + * + * For each data class annotated with [@DBRow][com.ctrip.sqllin.dsl.annotation.DBRow] + * and [@Serializable][kotlinx.serialization.Serializable], this processor generates + * a companion `Table` object (named `{ClassName}Table`) with: + * + * - Type-safe column property accessors for SELECT clauses + * - Mutable properties for UPDATE SET clauses + * - Primary key metadata extraction from [@PrimaryKey][com.ctrip.sqllin.dsl.annotation.PrimaryKey] + * and [@CompositePrimaryKey][com.ctrip.sqllin.dsl.annotation.CompositePrimaryKey] annotations + * + * The generated code provides compile-time safety for SQL DSL operations. + * * @author Yuang Qiao */ - class ClauseProcessor( private val environment: SymbolProcessorEnvironment, ) : SymbolProcessor { + /** + * Annotation names and validation messages used during processing. + */ private companion object { const val ANNOTATION_DATABASE_ROW_NAME = "com.ctrip.sqllin.dsl.annotation.DBRow" const val ANNOTATION_PRIMARY_KEY = "com.ctrip.sqllin.dsl.annotation.PrimaryKey" @@ -47,6 +61,12 @@ class ClauseProcessor( const val PROMPT_PRIMARY_KEY_USE_COUNT = "You only could use PrimaryKey to annotate one property in a class." } + /** + * Processes all [@DBRow][com.ctrip.sqllin.dsl.annotation.DBRow] annotated classes + * and generates corresponding table objects. + * + * @return List of symbols that couldn't be processed (due to validation failures) + */ @Suppress("UNCHECKED_CAST") override fun process(resolver: Resolver): List { val allDBRowClasses = resolver.getSymbolsWithAnnotation(ANNOTATION_DATABASE_ROW_NAME) @@ -176,6 +196,11 @@ class ClauseProcessor( return invalidateDBRowClasses } + /** + * Maps a property's Kotlin type to the corresponding clause element type name. + * + * @return The clause type name (ClauseNumber, ClauseString, ClauseBoolean), or null if unsupported + */ private fun getClauseElementTypeStr(property: KSPropertyDeclaration): String? = when ( property.typeName ) { @@ -198,6 +223,11 @@ class ClauseProcessor( else -> null } + /** + * Generates the default getter value for SetClause properties based on type. + * + * @return The default value string for the property type, or null if unsupported + */ private fun getSetClauseGetterValue(property: KSPropertyDeclaration): String? = when ( property.typeName ) { @@ -219,6 +249,14 @@ class ClauseProcessor( else -> null } + /** + * Generates the appropriate append function call for SetClause setters. + * + * @param elementName The serialized element name + * @param property The property declaration + * @param isNotNull Whether the property is non-nullable + * @return The append function call string, or null if unsupported type + */ private fun appendFunction(elementName: String, property: KSPropertyDeclaration, isNotNull: Boolean): String? = when (property.typeName) { Int::class.qualifiedName, Long::class.qualifiedName, @@ -238,6 +276,9 @@ class ClauseProcessor( else -> null } + /** + * Extension property that resolves a property's fully qualified type name. + */ private inline val KSPropertyDeclaration.typeName get() = type.resolve().declaration.qualifiedName?.asString() } \ No newline at end of file diff --git a/sqllin-processor/src/main/kotlin/com/ctrip/sqllin/processor/ClauseProcessorProvider.kt b/sqllin-processor/src/main/kotlin/com/ctrip/sqllin/processor/ClauseProcessorProvider.kt index 1b567be..6d936cb 100644 --- a/sqllin-processor/src/main/kotlin/com/ctrip/sqllin/processor/ClauseProcessorProvider.kt +++ b/sqllin-processor/src/main/kotlin/com/ctrip/sqllin/processor/ClauseProcessorProvider.kt @@ -21,10 +21,13 @@ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.processing.SymbolProcessorProvider /** - * This module's entry point - * @author yaqiao + * KSP (Kotlin Symbol Processing) provider for [ClauseProcessor]. + * + * This is the entry point for the sqllin-processor KSP plugin, which generates + * table objects with type-safe column accessors for data classes annotated with [@DBRow][com.ctrip.sqllin.dsl.annotation.DBRow]. + * + * @author Yuang Qiao */ - class ClauseProcessorProvider : SymbolProcessorProvider { override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor =