chore: rework docs numbering (#70295)
This commit is contained in:
@@ -22,28 +22,28 @@
|
||||
**Timeline:** 2-3 days
|
||||
|
||||
**Steps:**
|
||||
1. **1-getting-started.md** (Phases 0-1, ~4 hours)
|
||||
1. **1-getting-started.md** (Setup Phases 1-2, ~4 hours)
|
||||
- Project scaffolding and build setup
|
||||
- Spec operation implementation
|
||||
- **Milestone:** `./destination-{db} --spec` works
|
||||
|
||||
2. **2-database-setup.md** (Phases 2-5, ~6 hours)
|
||||
- Database connectivity and basic operations
|
||||
- Namespace and table operations
|
||||
2. **2-database-setup.md** (Database Phases 1-2, ~6 hours)
|
||||
- Database connectivity and all table operations
|
||||
- Check operation implementation
|
||||
- **Milestone:** `./destination-{db} --check --config config.json` works
|
||||
|
||||
3. **3-write-infrastructure.md** (Phases 6-7, ~4 hours)
|
||||
3. **3-write-infrastructure.md** (Infrastructure Phases 1-2, ~4 hours)
|
||||
- Name generators and DI setup
|
||||
- Write operation infrastructure
|
||||
- Understanding test contexts
|
||||
- **Milestone:** DI configured, ready for business logic
|
||||
|
||||
4. **4-write-operations.md** (Phases 8-11, ~8 hours)
|
||||
4. **4-write-operations.md** (Write Phases 1-4, ~8 hours)
|
||||
- InsertBuffer, Aggregate, Writer implementation
|
||||
- Append mode (direct writes)
|
||||
- Generation ID support
|
||||
- Overwrite mode (atomic swap)
|
||||
- Copy operation
|
||||
- **Milestone:** `./destination-{db} --write` works with append + overwrite modes
|
||||
|
||||
**Result:** Working connector suitable for PoC and simple use cases
|
||||
@@ -59,14 +59,14 @@
|
||||
**Steps:**
|
||||
1-4. Complete Fast Path (above)
|
||||
|
||||
5. **5-advanced-features.md** (Phases 12-15, ~12 hours)
|
||||
5. **5-advanced-features.md** (Advanced Phases 1-4, ~12 hours)
|
||||
- Schema evolution (automatic column add/drop/modify)
|
||||
- Dedupe mode (MERGE with primary key)
|
||||
- CDC support (hard/soft deletes)
|
||||
- Optimization and polish
|
||||
- **Milestone:** Full-featured, production-ready connector
|
||||
|
||||
6. **6-testing.md** (~2 hours)
|
||||
6. **6-testing.md** (Testing Phase 1, ~2 hours)
|
||||
- Run BasicFunctionalityIntegrationTest
|
||||
- Validate all sync modes
|
||||
- Test schema evolution and CDC
|
||||
@@ -98,12 +98,12 @@
|
||||
|
||||
| Guide | Phases | What Works | Lines | Time | Prerequisites |
|
||||
|-------|--------|------------|-------|------|---------------|
|
||||
| **1-getting-started.md** | 0-1 | --spec | ~626 | 4h | None |
|
||||
| **2-database-setup.md** | 2-5 | --check | ~902 | 6h | Guide 1 |
|
||||
| **3-write-infrastructure.md** | 6-7 | DI ready | ~855 | 4h | Guide 2 |
|
||||
| **4-write-operations.md** | 8-11 | --write (append, overwrite) | ~869 | 8h | Guide 3 |
|
||||
| **5-advanced-features.md** | 12-15 | All features | ~1020 | 12h | Guide 4 |
|
||||
| **6-testing.md** | Tests | All tests pass | ~878 | 2h | Guide 5 |
|
||||
| **1-getting-started.md** | Setup 1-2 | --spec | ~626 | 4h | None |
|
||||
| **2-database-setup.md** | Database 1-2 | --check | ~1180 | 6h | Guide 1 |
|
||||
| **3-write-infrastructure.md** | Infrastructure 1-2 | DI ready | ~600 | 4h | Guide 2 |
|
||||
| **4-write-operations.md** | Write 1-4 | --write (append, overwrite) | ~780 | 8h | Guide 3 |
|
||||
| **5-advanced-features.md** | Advanced 1-4 | All features | ~900 | 12h | Guide 4 |
|
||||
| **6-testing.md** | Testing 1 | All tests pass | ~730 | 2h | Guide 5 |
|
||||
| **7-troubleshooting.md** | Reference | Debug help | ~280 | As needed | Any |
|
||||
|
||||
---
|
||||
@@ -234,7 +234,7 @@ Platform → stdin → Lifecycle → Writer.setup()
|
||||
- Look at destination-snowflake or destination-clickhouse for examples
|
||||
|
||||
**Common pitfalls:**
|
||||
- Not reading test contexts section (causes confusion in Phase 7)
|
||||
- Not reading test contexts section (causes confusion in Infrastructure Phase 2)
|
||||
- Missing DI registration (causes "No bean found" errors)
|
||||
- Skipping CDK version pinning (causes build issues)
|
||||
- Not understanding StreamLoader variants (causes wrong finalization)
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Scaffolding
|
||||
## Setup Phase 1: Scaffolding
|
||||
|
||||
**Goal:** Empty project structure that builds
|
||||
|
||||
**Checkpoint:** Project compiles
|
||||
|
||||
### Step 0.1: Create Directory Structure
|
||||
### Setup Step 1: Create Directory Structure
|
||||
|
||||
```bash
|
||||
cd airbyte-integrations/connectors
|
||||
@@ -34,7 +34,7 @@ mkdir check client config dataflow spec write
|
||||
mkdir write/load write/transform
|
||||
```
|
||||
|
||||
### Step 0.2: Create gradle.properties with CDK Version Pin
|
||||
### Setup Step 2: Create gradle.properties with CDK Version Pin
|
||||
|
||||
**File:** `destination-{db}/gradle.properties`
|
||||
|
||||
@@ -56,7 +56,7 @@ cdkVersion=0.1.76
|
||||
./gradlew destination-{db}:upgradeCdk --cdkVersion=0.1.76
|
||||
```
|
||||
|
||||
### Step 0.3: Create build.gradle.kts
|
||||
### Setup Step 3: Create build.gradle.kts
|
||||
|
||||
**File:** `destination-{db}/build.gradle.kts`
|
||||
|
||||
@@ -88,7 +88,7 @@ dependencies {
|
||||
|
||||
**No need to manually declare CDK dependencies** - the plugin handles it
|
||||
|
||||
### Step 0.4: Create metadata.yaml
|
||||
### Setup Step 4: Create metadata.yaml
|
||||
|
||||
**File:** `destination-{db}/metadata.yaml`
|
||||
|
||||
@@ -145,7 +145,7 @@ metadataSpecVersion: "1.0"
|
||||
grep "baseImage:" airbyte-integrations/connectors/destination-*/metadata.yaml | sort | uniq -c | sort -rn | head -3
|
||||
```
|
||||
|
||||
### Step 0.5: Configure Docker Build in build.gradle.kts
|
||||
### Setup Step 5: Configure Docker Build in build.gradle.kts
|
||||
|
||||
**File:** Update `destination-{db}/build.gradle.kts`
|
||||
|
||||
@@ -181,7 +181,7 @@ dependencies {
|
||||
- `io.airbyte.gradle.docker`: Provides Docker build tasks
|
||||
- `airbyte-connector-docker-convention`: Reads metadata.yaml, generates build args
|
||||
|
||||
### Step 0.6: Create Main Entry Point
|
||||
### Setup Step 6: Create Main Entry Point
|
||||
|
||||
**File:** `destination-{db}/src/main/kotlin/.../{DB}Destination.kt`
|
||||
|
||||
@@ -197,7 +197,7 @@ fun main(args: Array<String>) {
|
||||
|
||||
**That's it!** The framework handles everything else.
|
||||
|
||||
### Step 0.7: Verify Build
|
||||
### Setup Step 7: Verify Build
|
||||
|
||||
```bash
|
||||
$ ./gradlew :destination-{db}:build
|
||||
@@ -211,7 +211,7 @@ $ ./gradlew :destination-{db}:build
|
||||
- Micronaut scanning issues? Ensure `@Singleton` annotations present
|
||||
- metadata.yaml syntax errors? Validate YAML format
|
||||
|
||||
### Step 0.8: Create application-connector.yml
|
||||
### Setup Step 8: Create application-connector.yml
|
||||
|
||||
**File:** `src/main/resources/application-connector.yml`
|
||||
|
||||
@@ -257,7 +257,7 @@ Failed to inject value for parameter [fileTransferEnabled]
|
||||
- ✅ `mappers.namespace-mapping-config-path`: Namespace mapping file path (empty for identity)
|
||||
- ✅ `file-transfer.enabled`: Whether connector transfers files (false for databases)
|
||||
|
||||
### Step 0.9: Build Docker Image
|
||||
### Setup Step 9: Build Docker Image
|
||||
|
||||
```bash
|
||||
$ ./gradlew :destination-{db}:assemble
|
||||
@@ -291,13 +291,13 @@ airbyte/destination-{db} 0.1.0 abc123def456 2 minutes ago 500MB
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Spec Operation
|
||||
## Setup Phase 2: Spec Operation
|
||||
|
||||
**Goal:** Implement --spec operation (returns connector configuration schema)
|
||||
|
||||
**Checkpoint:** Spec test passes
|
||||
|
||||
### Step 1.1: Understand Configuration Classes
|
||||
### Setup Step 1: Understand Configuration Classes
|
||||
|
||||
**Two classes work together for configuration:**
|
||||
|
||||
@@ -317,7 +317,7 @@ ConfigurationFactory parses JSON → Configuration object
|
||||
Your code uses Configuration object
|
||||
```
|
||||
|
||||
### Step 1.2: Create Specification Class
|
||||
### Setup Step 2: Create Specification Class
|
||||
|
||||
**Purpose:** Defines the configuration form users fill in Airbyte UI
|
||||
|
||||
@@ -361,7 +361,7 @@ open class {DB}Specification : ConfigurationSpecification() {
|
||||
- `@JsonSchemaTitle("Title")` - Label in UI (optional, defaults to property name)
|
||||
- `@JsonSchemaInject(json = """{"airbyte_secret": true}""")` - Mark as secret (passwords, API keys)
|
||||
|
||||
### Step 1.3: Create Configuration and Factory
|
||||
### Setup Step 3: Create Configuration and Factory
|
||||
|
||||
**Purpose:** Runtime configuration object your code actually uses
|
||||
|
||||
@@ -411,7 +411,7 @@ class {DB}ConfigurationFactory :
|
||||
- Specification = What users configure
|
||||
- Configuration = What your code uses
|
||||
|
||||
### Step 1.4: Create Specification Extension
|
||||
### Setup Step 4: Create Specification Extension
|
||||
|
||||
**Purpose:** Declares what sync modes your connector supports
|
||||
|
||||
@@ -443,7 +443,7 @@ class {DB}SpecificationExtension : DestinationSpecificationExtension {
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.5: Configure Documentation URL
|
||||
### Setup Step 5: Configure Documentation URL
|
||||
|
||||
**File:** `src/main/resources/application.yml`
|
||||
|
||||
@@ -472,7 +472,7 @@ airbyteBulkConnector {
|
||||
|
||||
**Default:** If not specified, uses placeholder URL
|
||||
|
||||
### Step 1.6: Create Expected Spec Test File
|
||||
### Setup Step 6: Create Expected Spec Test File
|
||||
|
||||
**File:** `src/test-integration/resources/expected-spec-oss.json`
|
||||
|
||||
@@ -539,7 +539,7 @@ airbyteBulkConnector {
|
||||
2. Copying output to this file
|
||||
3. Using it for regression testing
|
||||
|
||||
### Step 1.7: Create Spec Test
|
||||
### Setup Step 7: Create Spec Test
|
||||
|
||||
**File:** `src/test-integration/kotlin/.../spec/{DB}SpecTest.kt`
|
||||
|
||||
@@ -557,7 +557,7 @@ class {DB}SpecTest : SpecTest()
|
||||
- Matches expected-spec-oss.json (snapshot test)
|
||||
- If Cloud-specific: Matches expected-spec-cloud.json
|
||||
|
||||
### Step 1.8: Generate and Validate Spec
|
||||
### Setup Step 8: Generate and Validate Spec
|
||||
|
||||
**Run spec operation to generate the JSON schema:**
|
||||
|
||||
@@ -590,7 +590,7 @@ mkdir -p src/test-integration/resources
|
||||
|
||||
**Tip:** Use `jq` to format: `./gradlew :destination-{db}:run --args='--spec' | jq .spec > expected-spec-oss.json`
|
||||
|
||||
### Step 1.9: Run Spec Test
|
||||
### Setup Step 9: Run Spec Test
|
||||
|
||||
```bash
|
||||
$ ./gradlew :destination-{db}:integrationTestSpecOss
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ After completing this guide, you'll have:
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Name Generators & TableCatalog DI
|
||||
## Infrastructure Phase 1: Name Generators & TableCatalog DI
|
||||
|
||||
**Goal:** Create name generator beans required for TableCatalog instantiation
|
||||
|
||||
@@ -25,7 +25,7 @@ After completing this guide, you'll have:
|
||||
|
||||
Without these beans, you'll get **"Error instantiating TableCatalog"** or **"No bean of type [FinalTableNameGenerator]"** errors in Phase 7 write tests.
|
||||
|
||||
### Step 6.1: Create RawTableNameGenerator
|
||||
### Infrastructure Step 1: Create RawTableNameGenerator
|
||||
|
||||
**File:** `config/{DB}NameGenerators.kt`
|
||||
|
||||
@@ -58,7 +58,7 @@ class {DB}RawTableNameGenerator(
|
||||
- Modern connectors typically use final tables only, but interface must be implemented
|
||||
- Keep implementation simple (identity mapping is fine)
|
||||
|
||||
### Step 6.2: Create FinalTableNameGenerator
|
||||
### Infrastructure Step 2: Create FinalTableNameGenerator
|
||||
|
||||
**Add to same file:** `config/{DB}NameGenerators.kt`
|
||||
|
||||
@@ -90,7 +90,7 @@ class {DB}FinalTableNameGenerator(
|
||||
// Output: TableName("my_database", "customers") // Uses config.database as fallback
|
||||
```
|
||||
|
||||
### Step 6.3: Create ColumnNameGenerator
|
||||
### Infrastructure Step 3: Create ColumnNameGenerator
|
||||
|
||||
**Add to same file:** `config/{DB}NameGenerators.kt`
|
||||
|
||||
@@ -123,7 +123,7 @@ class {DB}ColumnNameGenerator : ColumnNameGenerator {
|
||||
"userId" → "userId"
|
||||
```
|
||||
|
||||
### Step 6.4: Add Name Transformation Helper
|
||||
### Infrastructure Step 4: Add Name Transformation Helper
|
||||
|
||||
**Add to same file:** `config/{DB}NameGenerators.kt`
|
||||
|
||||
@@ -177,7 +177,7 @@ private fun String.toDbCompatible(): String {
|
||||
private val POSTGRES_RESERVED_WORDS = setOf("user", "table", "select", ...)
|
||||
```
|
||||
|
||||
### Step 6.5: Register TempTableNameGenerator in BeanFactory
|
||||
### Infrastructure Step 5: Register TempTableNameGenerator in BeanFactory
|
||||
|
||||
**File:** Update `{DB}BeanFactory.kt`
|
||||
|
||||
@@ -200,7 +200,7 @@ fun tempTableNameGenerator(config: {DB}Configuration): TempTableNameGenerator {
|
||||
- CDK provides implementation, but YOU must register it
|
||||
- Used by Writer to create staging tables
|
||||
|
||||
### Step 6.6: Verify Compilation
|
||||
### Infrastructure Step 6: Verify Compilation
|
||||
|
||||
**Validate:**
|
||||
```bash
|
||||
@@ -227,7 +227,7 @@ $ ./gradlew :destination-{db}:integrationTest # testSpecOss, testSuccessConfigs
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Write Operation Infrastructure
|
||||
## Infrastructure Phase 2: Write Operation Infrastructure
|
||||
|
||||
**Goal:** Create write operation infrastructure beans (no business logic yet)
|
||||
|
||||
@@ -242,7 +242,7 @@ $ ./gradlew :destination-{db}:integrationTest # testSpecOss, testSuccessConfigs
|
||||
|
||||
**Key insight:** Separate infrastructure DI from business logic DI to catch errors incrementally.
|
||||
|
||||
### Step 7.1: Create WriteOperationV2
|
||||
### Infrastructure Step 1: Create WriteOperationV2
|
||||
|
||||
**File:** `cdk/WriteOperationV2.kt`
|
||||
|
||||
@@ -293,7 +293,7 @@ IllegalStateException: A legal sync requires a declared @Singleton of a type tha
|
||||
- Many connectors keep this file identical across databases
|
||||
- Signals "this is infrastructure, not business logic"
|
||||
|
||||
### Step 7.2: Create DatabaseInitialStatusGatherer
|
||||
### Infrastructure Step 2: Create DatabaseInitialStatusGatherer
|
||||
|
||||
**File:** `config/{DB}DirectLoadDatabaseInitialStatusGatherer.kt`
|
||||
|
||||
@@ -337,7 +337,7 @@ DirectLoadInitialStatus(
|
||||
|
||||
⚠️ **MISSING IN V1 GUIDE:** This step existed as code but bean registration was missing!
|
||||
|
||||
### Step 7.3: Register DatabaseInitialStatusGatherer in BeanFactory
|
||||
### Infrastructure Step 3: Register DatabaseInitialStatusGatherer in BeanFactory
|
||||
|
||||
**File:** Update `{DB}BeanFactory.kt`
|
||||
|
||||
@@ -363,7 +363,7 @@ fun initialStatusGatherer(
|
||||
- Micronaut needs explicit return type for generic beans
|
||||
- Factory method provides type safety
|
||||
|
||||
### Step 7.4: Create ColumnNameMapper
|
||||
### Infrastructure Step 4: Create ColumnNameMapper
|
||||
|
||||
**File:** `write/transform/{DB}ColumnNameMapper.kt`
|
||||
|
||||
@@ -407,7 +407,7 @@ class {DB}ColumnNameMapper(
|
||||
- ColumnNameMapper: Uses mappings during transform (Phase 7)
|
||||
- Separation of concerns: generation vs. application
|
||||
|
||||
### Step 7.5: Register AggregatePublishingConfig in BeanFactory
|
||||
### Infrastructure Step 5: Register AggregatePublishingConfig in BeanFactory
|
||||
|
||||
**File:** Update `{DB}BeanFactory.kt`
|
||||
|
||||
@@ -449,7 +449,7 @@ fun aggregatePublishingConfig(dataChannelMedium: DataChannelMedium): AggregatePu
|
||||
- Tune later based on performance requirements
|
||||
- Start with defaults - they work for most databases
|
||||
|
||||
### Step 7.6: Create WriteInitializationTest
|
||||
### Infrastructure Step 6: Create WriteInitializationTest
|
||||
|
||||
**File:** `src/test-integration/kotlin/.../write/{DB}WriteInitTest.kt`
|
||||
|
||||
@@ -498,7 +498,7 @@ Phase 7: WriteInitTest validates they work with real catalog
|
||||
Phase 8: ConnectorWiringSuite validates full write path with mock catalog
|
||||
```
|
||||
|
||||
### Step 7.6: Create Test Config File
|
||||
### Infrastructure Step 7: Create Test Config File
|
||||
|
||||
**File:** `secrets/config.json`
|
||||
|
||||
@@ -525,7 +525,7 @@ $ mkdir -p destination-{db}/secrets
|
||||
|
||||
**Note:** Add `secrets/` to `.gitignore` to avoid committing credentials
|
||||
|
||||
### Step 7.7: Validate WriteInitializationTest
|
||||
### Infrastructure Step 8: Validate WriteInitializationTest
|
||||
|
||||
**Validate:**
|
||||
```bash
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Writer & Append Mode (Business Logic)
|
||||
## Write Phase 1: Writer & Append Mode (Business Logic)
|
||||
|
||||
**Goal:** Implement actual data writing (Writer, Aggregate, InsertBuffer)
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
**Key insight:** Infrastructure DI (Phase 7) is separate from business logic DI (Phase 8).
|
||||
Phase 7 validates "can we start?" Phase 8 validates "can we write data?"
|
||||
|
||||
### Step 8.1: Create InsertBuffer
|
||||
### Write Step 1: Create InsertBuffer
|
||||
|
||||
**File:** `write/load/{DB}InsertBuffer.kt`
|
||||
|
||||
@@ -109,7 +109,7 @@ class {DB}InsertBuffer(
|
||||
- Buffers hold stream-specific state (table name, accumulated records)
|
||||
- AggregateFactory creates one buffer per stream
|
||||
|
||||
### Step 8.2: Add executeInsert() to Client
|
||||
### Write Step 2: Add executeInsert() to Client
|
||||
|
||||
**File:** Update `client/{DB}AirbyteClient.kt`
|
||||
|
||||
@@ -145,7 +145,7 @@ private fun setParameter(statement: PreparedStatement, index: Int, value: Airbyt
|
||||
|
||||
**Note:** For non-JDBC databases, use native client APIs (e.g., MongoDB insertOne, ClickHouse native client)
|
||||
|
||||
### Step 8.3: Create Aggregate
|
||||
### Write Step 3: Create Aggregate
|
||||
|
||||
**File:** `dataflow/{DB}Aggregate.kt`
|
||||
|
||||
@@ -199,7 +199,7 @@ InsertBuffer.accumulate()
|
||||
Database
|
||||
```
|
||||
|
||||
### Step 8.4: Create AggregateFactory
|
||||
### Write Step 4: Create AggregateFactory
|
||||
|
||||
**File:** `dataflow/{DB}AggregateFactory.kt`
|
||||
|
||||
@@ -249,7 +249,7 @@ class {DB}AggregateFactory(
|
||||
- Can't use constructor injection (dynamic stream list)
|
||||
- Factory receives StoreKey, looks up stream config, creates Aggregate
|
||||
|
||||
### Step 8.5: Create Writer
|
||||
### Write Step 5: Create Writer
|
||||
|
||||
**File:** `write/{DB}Writer.kt`
|
||||
|
||||
@@ -352,7 +352,7 @@ class {DB}Writer(
|
||||
- DirectLoadTableDedupStreamLoader
|
||||
- DirectLoadTableDedupTruncateStreamLoader
|
||||
|
||||
### Step 8.6: Create ConnectorWiringSuite Test
|
||||
### Write Step 6: Create ConnectorWiringSuite Test
|
||||
|
||||
**File:** `src/test-integration/kotlin/.../component/{DB}WiringTest.kt`
|
||||
|
||||
@@ -433,7 +433,7 @@ class {DB}WiringTest(
|
||||
- Creates dynamic test streams
|
||||
- Focuses on write logic, not catalog parsing
|
||||
|
||||
### Step 8.7: Validate ConnectorWiringSuite
|
||||
### Write Step 7: Validate ConnectorWiringSuite
|
||||
|
||||
**Validate:**
|
||||
```bash
|
||||
@@ -471,7 +471,7 @@ $ ./gradlew :destination-{db}:integrationTest # 3 tests should pass
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: Generation ID Support
|
||||
## Write Phase 2: Generation ID Support
|
||||
|
||||
**Goal:** Track sync generations for refresh handling
|
||||
|
||||
@@ -486,7 +486,7 @@ $ ./gradlew :destination-{db}:integrationTest # 3 tests should pass
|
||||
- Full refresh: minimumGenerationId = generationId (replace all data)
|
||||
- Incremental: minimumGenerationId = 0 (keep all data)
|
||||
|
||||
### Step 9.1: Enable Generation ID Test
|
||||
### Write Step 1: Enable Generation ID Test
|
||||
|
||||
**File:** Update `src/test-integration/kotlin/.../component/{DB}TableOperationsTest.kt`
|
||||
|
||||
@@ -502,7 +502,7 @@ override fun `get generation id`() {
|
||||
- Returns 0L for tables without generation ID
|
||||
- Returns actual generation ID from `_airbyte_generation_id` column
|
||||
|
||||
### Step 9.2: Validate
|
||||
### Write Step 2: Validate
|
||||
|
||||
**Validate:**
|
||||
```bash
|
||||
@@ -516,7 +516,7 @@ $ ./gradlew :destination-{db}:componentTest # 10 tests should pass
|
||||
|
||||
---
|
||||
|
||||
## Phase 10: Overwrite Mode
|
||||
## Write Phase 3: Overwrite Mode
|
||||
|
||||
**Goal:** Support full refresh (replace all data)
|
||||
|
||||
@@ -531,7 +531,7 @@ $ ./gradlew :destination-{db}:componentTest # 10 tests should pass
|
||||
- **Append** (Phase 8): INSERT into existing table
|
||||
- **Overwrite** (Phase 10): SWAP temp table with final table
|
||||
|
||||
### Step 10.1: Implement overwriteTable() in SQL Generator
|
||||
### Write Step 1: Implement overwriteTable() in SQL Generator
|
||||
|
||||
**File:** Update `client/{DB}SqlGenerator.kt`
|
||||
|
||||
@@ -571,7 +571,7 @@ fun overwriteTable(source: TableName, target: TableName): List<String> {
|
||||
- **Postgres/MySQL**: DROP + RENAME requires transaction for atomicity
|
||||
- **BigQuery**: CREATE OR REPLACE TABLE (different pattern)
|
||||
|
||||
### Step 10.2: Implement overwriteTable() in Client
|
||||
### Write Step 2: Implement overwriteTable() in Client
|
||||
|
||||
**File:** Update `client/{DB}AirbyteClient.kt`
|
||||
|
||||
@@ -585,7 +585,7 @@ override suspend fun overwriteTable(
|
||||
}
|
||||
```
|
||||
|
||||
### Step 10.3: Update Writer for Truncate Mode
|
||||
### Write Step 3: Update Writer for Truncate Mode
|
||||
|
||||
**File:** Update `write/{DB}Writer.kt`
|
||||
|
||||
@@ -637,7 +637,7 @@ override fun createStreamLoader(stream: DestinationStream): StreamLoader {
|
||||
- **AppendStreamLoader**: Writes directly to final table
|
||||
- **AppendTruncateStreamLoader**: Writes to temp table, then swaps
|
||||
|
||||
### Step 10.4: Enable Tests
|
||||
### Write Step 4: Enable Tests
|
||||
|
||||
**File:** Update `src/test-integration/kotlin/.../component/{DB}TableOperationsTest.kt`
|
||||
|
||||
@@ -648,7 +648,7 @@ override fun `overwrite tables`() {
|
||||
}
|
||||
```
|
||||
|
||||
### Step 10.5: Validate
|
||||
### Write Step 5: Validate
|
||||
|
||||
**Validate:**
|
||||
```bash
|
||||
@@ -663,7 +663,7 @@ $ ./gradlew :destination-{db}:integrationTest # 3 tests should pass
|
||||
|
||||
---
|
||||
|
||||
## Phase 11: Copy Operation
|
||||
## Write Phase 4: Copy Operation
|
||||
|
||||
**Goal:** Support table copying (used internally by some modes)
|
||||
|
||||
@@ -674,7 +674,7 @@ $ ./gradlew :destination-{db}:integrationTest # 3 tests should pass
|
||||
- Some overwrite implementations: Copy instead of swap
|
||||
- Schema evolution: Copy to new schema
|
||||
|
||||
### Step 11.1: Implement copyTable() in SQL Generator
|
||||
### Write Step 1: Implement copyTable() in SQL Generator
|
||||
|
||||
**File:** Update `client/{DB}SqlGenerator.kt`
|
||||
|
||||
@@ -724,7 +724,7 @@ fun copyTable(
|
||||
}
|
||||
```
|
||||
|
||||
### Step 11.2: Implement copyTable() in Client
|
||||
### Write Step 2: Implement copyTable() in Client
|
||||
|
||||
**File:** Update `client/{DB}AirbyteClient.kt`
|
||||
|
||||
@@ -738,7 +738,7 @@ override suspend fun copyTable(
|
||||
}
|
||||
```
|
||||
|
||||
### Step 11.3: Enable Test
|
||||
### Write Step 3: Enable Test
|
||||
|
||||
**File:** Update `src/test-integration/kotlin/.../component/{DB}TableOperationsTest.kt`
|
||||
|
||||
@@ -749,7 +749,7 @@ override fun `copy tables`() {
|
||||
}
|
||||
```
|
||||
|
||||
### Step 11.4: Validate
|
||||
### Write Step 4: Validate
|
||||
|
||||
**Validate:**
|
||||
```bash
|
||||
|
||||
@@ -22,6 +22,7 @@ After completing this guide, you'll have a production-ready connector with:
|
||||
|
||||
---
|
||||
|
||||
## Advanced Phase 1: Schema Evolution
|
||||
|
||||
**Goal:** Automatically adapt to schema changes
|
||||
|
||||
@@ -33,7 +34,7 @@ After completing this guide, you'll have a production-ready connector with:
|
||||
3. **Change type**: Source changes field type → alter column (with casting)
|
||||
4. **Change nullability**: Source changes nullable → alter column constraints
|
||||
|
||||
### Step 12.1: Implement discoverSchema()
|
||||
### Advanced Step 1: Implement discoverSchema()
|
||||
|
||||
**File:** Update `client/{DB}AirbyteClient.kt`
|
||||
|
||||
@@ -90,7 +91,7 @@ private val AIRBYTE_META_COLUMNS = setOf(
|
||||
- **ClickHouse**: `system.columns` or client API `getTableSchema()`
|
||||
- **BigQuery**: `INFORMATION_SCHEMA.COLUMNS`
|
||||
|
||||
### Step 12.2: Implement computeSchema()
|
||||
### Advanced Step 2: Implement computeSchema()
|
||||
|
||||
**File:** Update `client/{DB}AirbyteClient.kt`
|
||||
|
||||
@@ -117,7 +118,7 @@ override fun computeSchema(
|
||||
- Applies column name mapping (Phase 6 generators)
|
||||
- Uses ColumnUtils.toDialectType() from Phase 4
|
||||
|
||||
### Step 12.3: Implement alterTable() - ADD COLUMN
|
||||
### Advanced Step 3: Implement alterTable() - ADD COLUMN
|
||||
|
||||
**File:** Update `client/{DB}SqlGenerator.kt`
|
||||
|
||||
@@ -154,7 +155,7 @@ fun alterTable(
|
||||
}
|
||||
```
|
||||
|
||||
### Step 12.4: Implement alterTable() - MODIFY COLUMN
|
||||
### Advanced Step 4: Implement alterTable() - MODIFY COLUMN
|
||||
|
||||
**Add to alterTable():**
|
||||
|
||||
@@ -245,7 +246,7 @@ fun recreateTable(
|
||||
}
|
||||
```
|
||||
|
||||
### Step 12.5: Implement applyChangeset()
|
||||
### Advanced Step 5: Implement applyChangeset()
|
||||
|
||||
**File:** Update `client/{DB}AirbyteClient.kt`
|
||||
|
||||
@@ -275,7 +276,7 @@ override suspend fun applyChangeset(
|
||||
}
|
||||
```
|
||||
|
||||
### Step 12.6: Implement ensureSchemaMatches()
|
||||
### Advanced Step 6: Implement ensureSchemaMatches()
|
||||
|
||||
**File:** Update `client/{DB}AirbyteClient.kt`
|
||||
|
||||
@@ -303,7 +304,7 @@ override suspend fun ensureSchemaMatches(
|
||||
- If source schema changed since last sync, applies schema changes
|
||||
- Automatic - no user intervention needed
|
||||
|
||||
### Step 12.7: Validate Schema Evolution
|
||||
### Advanced Step 7: Validate Schema Evolution
|
||||
|
||||
**Validate:**
|
||||
```bash
|
||||
@@ -317,7 +318,7 @@ $ ./gradlew :destination-{db}:integrationTest # 3 tests should pass
|
||||
|
||||
---
|
||||
|
||||
## Phase 13: Dedupe Mode
|
||||
## Advanced Phase 2: Dedupe Mode
|
||||
|
||||
**Goal:** Support primary key deduplication
|
||||
|
||||
@@ -336,7 +337,7 @@ $ ./gradlew :destination-{db}:integrationTest # 3 tests should pass
|
||||
- **Overwrite** (Phase 10): Swap tables
|
||||
- **Dedupe** (Phase 13): Upsert with primary key
|
||||
|
||||
### Step 13.1: Implement upsertTable() in SQL Generator
|
||||
### Advanced Step 1: Implement upsertTable() in SQL Generator
|
||||
|
||||
**Option A: MERGE Statement (Snowflake, SQL Server, BigQuery)**
|
||||
|
||||
@@ -500,7 +501,7 @@ fun upsertTable(...): List<String> {
|
||||
}
|
||||
```
|
||||
|
||||
### Step 13.2: Implement upsertTable() in Client
|
||||
### Advanced Step 2: Implement upsertTable() in Client
|
||||
|
||||
**File:** Update `client/{DB}AirbyteClient.kt`
|
||||
|
||||
@@ -523,7 +524,7 @@ override suspend fun upsertTable(
|
||||
}
|
||||
```
|
||||
|
||||
### Step 13.3: Update Writer for Dedupe Mode
|
||||
### Advanced Step 3: Update Writer for Dedupe Mode
|
||||
|
||||
**File:** Update `write/{DB}Writer.kt`
|
||||
|
||||
@@ -585,7 +586,7 @@ override fun createStreamLoader(stream: DestinationStream): StreamLoader {
|
||||
- DirectLoadTableDedupStreamLoader (incremental dedupe)
|
||||
- DirectLoadTableDedupTruncateStreamLoader (full refresh dedupe)
|
||||
|
||||
### Step 13.4: Enable Tests
|
||||
### Advanced Step 4: Enable Tests
|
||||
|
||||
**File:** Update `src/test-integration/kotlin/.../component/{DB}TableOperationsTest.kt`
|
||||
|
||||
@@ -596,7 +597,7 @@ override fun `upsert tables`() {
|
||||
}
|
||||
```
|
||||
|
||||
### Step 13.5: Validate
|
||||
### Advanced Step 5: Validate
|
||||
|
||||
**Validate:**
|
||||
```bash
|
||||
@@ -611,7 +612,7 @@ $ ./gradlew :destination-{db}:integrationTest # 3 tests should pass
|
||||
|
||||
---
|
||||
|
||||
## Phase 14: CDC Support (Optional)
|
||||
## Advanced Phase 3: CDC Support (Optional)
|
||||
|
||||
**Goal:** Handle source deletions
|
||||
|
||||
@@ -624,7 +625,7 @@ $ ./gradlew :destination-{db}:integrationTest # 3 tests should pass
|
||||
- **Hard delete**: Remove record from destination
|
||||
- **Soft delete**: Keep record with deletion timestamp
|
||||
|
||||
### Step 14.1: Add CDC Configuration
|
||||
### Advanced Step 1: Add CDC Configuration
|
||||
|
||||
**File:** Update `spec/{DB}Specification.kt`
|
||||
|
||||
@@ -659,7 +660,7 @@ override fun makeWithoutExceptionHandling(pojo: {DB}Specification): {DB}Configur
|
||||
}
|
||||
```
|
||||
|
||||
### Step 14.2: Add CDC Logic to upsertTable()
|
||||
### Advanced Step 2: Add CDC Logic to upsertTable()
|
||||
|
||||
**File:** Update `client/{DB}SqlGenerator.kt`
|
||||
|
||||
@@ -726,11 +727,11 @@ private val CDC_DELETED_AT_COLUMN = "_ab_cdc_deleted_at"
|
||||
- Skip INSERT for deleted records (don't re-insert deleted rows)
|
||||
- Soft delete: No special clauses (just upsert the deletion record with timestamp)
|
||||
|
||||
### Step 14.3: Test CDC
|
||||
### Advanced Step 3: Test CDC
|
||||
|
||||
CDC tests are typically included in integration tests automatically if you have CDC streams configured. No separate test enablement needed - the framework tests CDC if the stream has `_ab_cdc_deleted_at` column.
|
||||
|
||||
### Step 14.4: Validate
|
||||
### Advanced Step 4: Validate
|
||||
|
||||
**Validate:**
|
||||
```bash
|
||||
@@ -744,7 +745,7 @@ $ ./gradlew :destination-{db}:integrationTest # 3 tests should pass (CDC tested
|
||||
|
||||
---
|
||||
|
||||
## Phase 15: Optimization & Polish
|
||||
## Advanced Phase 4: Optimization & Polish
|
||||
|
||||
**Goal:** Production-ready performance
|
||||
|
||||
|
||||
@@ -57,7 +57,9 @@ Before starting, you must have:
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Implement Test Helper Classes
|
||||
## Testing Phase 1: BasicFunctionalityIntegrationTest
|
||||
|
||||
### Testing Step 1: Implement Test Helper Classes
|
||||
|
||||
### Step 1.1: Create DestinationDataDumper
|
||||
|
||||
@@ -210,7 +212,7 @@ class {DB}Cleaner(
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Create BasicFunctionalityIntegrationTest Class
|
||||
### Testing Step 2: Create BasicFunctionalityIntegrationTest Class
|
||||
|
||||
### Step 2.1: Understand Required Parameters
|
||||
|
||||
@@ -346,7 +348,7 @@ class {DB}BasicFunctionalityTest : BasicFunctionalityIntegrationTest(
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Configure Test Parameters
|
||||
### Testing Step 3: Configure Test Parameters
|
||||
|
||||
### Quick Reference Table
|
||||
|
||||
@@ -531,7 +533,7 @@ AllTypesBehavior.StronglyTyped(
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Run Tests
|
||||
### Testing Step 4: Run Tests
|
||||
|
||||
### Test Individually
|
||||
|
||||
@@ -558,7 +560,7 @@ $ ./gradlew :destination-{db}:integrationTest --tests "*BasicFunctionalityTest"
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Debug Common Failures
|
||||
### Testing Step 5: Debug Common Failures
|
||||
|
||||
### Test: testAppend fails with "Record mismatch"
|
||||
|
||||
@@ -598,7 +600,7 @@ $ ./gradlew :destination-{db}:integrationTest --tests "*BasicFunctionalityTest"
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Optional Test Customization
|
||||
### Testing Step 6: Optional Test Customization
|
||||
|
||||
### Skip Tests Not Applicable
|
||||
|
||||
|
||||
Reference in New Issue
Block a user