MSSQL Source Connector - #3851
Conversation
|
Thanks for the PR. It is labeled Slash commands (own line, regular comment) move it around the queue:
See CONTRIBUTING.md for details. |
|
@lsabi please make this pr as draft or close it and add test, then reopen |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3851 +/- ##
============================================
- Coverage 83.67% 80.36% -3.31%
+ Complexity 1358 1340 -18
============================================
Files 1212 1218 +6
Lines 165138 164790 -348
Branches 132612 132723 +111
============================================
- Hits 138181 132439 -5742
- Misses 23308 28803 +5495
+ Partials 3649 3548 -101
🚀 New features to boost your workflow:
|
|
@lukaszzborek I'm afraid but I can't test it on my locale. I've spent a day understanding the docs, as it's not super clear how one should do in order to build their own connector. I've then let my laptop run for almost a day and it did not finish compiling, just excessive heating. I managed to compile |
|
@lsabi, quite intresting. Did you join to our discord? there are many people which can help. You can find discord invite in readme. Also you can merge current master to yours, because you have old version of server. And please write on discord what machine do you have (where you compile code) |
hubcio
left a comment
There was a problem hiding this comment.
took this for a spin against a real sql server 2022 container with cdc enabled. open() connects fine, but as shipped poll() never returns a single message - it took five local fixes to get the first record through, and the payloads were still mangled. detailed comments inline.
| FROM [{db}].cdc.[{table}] t | ||
| WHERE {query_filter} | ||
| GROUP BY [__$start_lsn], [__$command_id], [__$seqval], [__$end_lsn], [__$update_mask] | ||
| ORDER BY [__$start_lsn] ASC, [__$command_id] ASC;"; |
There was a problem hiding this comment.
leftover ;"; inside the sql string - every poll fails with error 105 "unclosed quotation mark after the character string". ran the connector against sql server 2022 with cdc enabled: as shipped it never produces a single message.
| .flatten() | ||
| .collect::<Vec<_>>(); | ||
|
|
||
| let tables: Vec<String> = self.config.capture_table_columns.values().cloned().collect(); |
There was a problem hiding this comment.
this iterates the map's values (the column lists) as table names, so the query becomes FROM [testdb].cdc.[id,name,active] and fails with error 208 "invalid object name".
| // For each monitored table, get the capture columns | ||
| let table_columns = self.query(Query::new( | ||
| format!(r#" | ||
| SELECT OBJECT_NAME(ct.object_id, DB_ID('{database}')) as table_name, c.object_id, ct.capture_instance, ct.start_lsn, c.columns FROM [{database}].[cdc].[change_tables] as ct JOIN (SELECT object_id, STRING_AGG(column_name, ',') as columns FROM [{database}].[cdc].[captured_columns] GROUP BY object_id) as c ON c.object_id = ct.object_id;"# |
There was a problem hiding this comment.
OBJECT_NAME(ct.object_id, DB_ID(...)) names the change table (dbo_users_CT), not the source table - in cdc.change_tables, object_id is the change table's id and source_object_id is the source table's. so capture_table_columns ends up keyed by change-table names and a user-provided entry never matches the lookup on line 411. use ct.source_object_id.
There was a problem hiding this comment.
Since MSSQL has the source table and the change table with different names (by default _ct get's appended), either the user inserts the change table name or, there's need to make an internal conversion (while keeping a mapping of the names). Which one is the preferred one?
| let changes_query = format!( | ||
| r#"SELECT | ||
| TODATETIMEOFFSET( | ||
| [#db].sys.fn_cdc_map_lsn_to_time([__$start_lsn]), |
There was a problem hiding this comment.
#db is only substituted in the max-lsn query; here it reaches the server literally and fails with error 4121. the surrounding query already interpolates {db} via format!.
| None => return Err(Error::InvalidRecord) | ||
| }; | ||
|
|
||
| let operation_type = match r.get::<i64, _>("[__$operation]").unwrap_or(0) { |
There was a problem hiding this comment.
this panics instead of erroring: the result column is __$operation (no brackets) and it's an int, so both the name and the i64 type are wrong. tiberius get is try_get(..).unwrap(), and try_get errors on an unknown column name as well as on a failed conversion - so unwrap_or(0) is unreachable. the panic lands inside the task the sdk spawns for poll(), killing the polling task silently: the connector still reports as running and never produces again. use try_get with i32 and return an error.
| self.id | ||
| ); | ||
| } | ||
| "polling" => { |
There was a problem hiding this comment.
"polling" is accepted here but poll() returns Err(InvalidConfig) forever since poll_tables is commented out - reject the mode in open() until it exists.
| format!("[__$start_lsn] <= {n_lsn}") | ||
| }; | ||
|
|
||
| let capture_ops = self |
There was a problem hiding this comment.
capture_ops is built but never used - the capture_operations filter is silently ignored and every operation type gets streamed.
| } | ||
|
|
||
|
|
||
| /* |
There was a problem hiding this comment.
over half the file is commented-out postgres source code (poll_tables, sqlx type mapping, nearly all tests) - please delete it, git keeps history. the crate also fails cargo fmt --check (tabs) and cargo clippy -- -D warnings (21 errors), both are ci gates.
|
|
||
| [dependencies] | ||
| async-trait = { workspace = true } | ||
| base64 = { workspace = true } |
There was a problem hiding this comment.
base64 is only referenced inside commented-out code - dead dependency, drop it. machete won't catch this one: its heuristic matches the crate name inside comments, so the dead code keeps it looking used.
| "core/connectors/sources/elasticsearch_source", | ||
| "core/connectors/sources/influxdb_source", | ||
| "core/connectors/sources/postgres_source", | ||
| "core/connectors/sources/mssql_source", |
There was a problem hiding this comment.
Cargo.lock isn't part of the PR - building adds ~238 lines (tiberius, async-native-tls, connection-string, ...). commit the lock update.
Which issue does this PR address?
Partially Closes # #3573
Relates to #
Rationale
This is just the implementation of the source connector for the
cdc. I'm also working on the table polling, but it'll require more time.I don't have a database instance to test it, but tests are passing. If someone, who has a test
MSSQLinstance can try it out, it would be great.What changed?
Added the
Local Execution
AI Usage
None