|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | 3 | import copy |
| 4 | +from enum import IntEnum |
4 | 5 | import fnmatch |
5 | | -from typing import List |
| 6 | +from typing import List, Optional |
6 | 7 |
|
7 | 8 | import pyarrow as pa |
8 | 9 |
|
9 | 10 | from cloudquery.sdk.schema import arrow |
10 | 11 | from .column import Column |
11 | 12 |
|
12 | 13 |
|
| 14 | +CQ_SYNC_TIME_COLUMN = "cq_sync_time" |
| 15 | +CQ_SOURCE_NAME_COLUMN = "cq_source_name" |
| 16 | + |
| 17 | + |
13 | 18 | class Client: |
14 | 19 | pass |
15 | 20 |
|
@@ -192,9 +197,137 @@ def filter_dfs_child(r, matched, include, exclude, skip_dependent_tables): |
192 | 197 | return None |
193 | 198 |
|
194 | 199 |
|
195 | | -def flatten_tables(tables: List[Table]) -> List[Table]: |
196 | | - flattened: List[Table] = [] |
| 200 | +class TableColumnChangeType: |
| 201 | + ADD = 1 |
| 202 | + REMOVE = 2 |
| 203 | + REMOVE_UNIQUE_CONSTRAINT = 3 |
| 204 | + |
| 205 | + |
| 206 | +class TableColumnChange: |
| 207 | + def __init__( |
| 208 | + self, |
| 209 | + type: TableColumnChangeType, |
| 210 | + column_name: str, |
| 211 | + current: Optional[Column], |
| 212 | + previous: Optional[Column], |
| 213 | + ): |
| 214 | + self.type = type |
| 215 | + self.column_name = column_name |
| 216 | + self.current = current |
| 217 | + self.previous = previous |
| 218 | + |
| 219 | + |
| 220 | +class TableColumnChangeType(IntEnum): |
| 221 | + UNKNOWN = 0 |
| 222 | + ADD = 1 |
| 223 | + UPDATE = 2 |
| 224 | + REMOVE = 3 |
| 225 | + REMOVE_UNIQUE_CONSTRAINT = 4 |
| 226 | + MOVE_TO_CQ_ONLY = 5 |
| 227 | + |
| 228 | + |
| 229 | +def get_table_changes(new: Table, old: Table) -> List[TableColumnChange]: |
| 230 | + changes = [] |
| 231 | + |
| 232 | + # Special case: Moving from individual PKs to singular PK on _cq_id |
| 233 | + new_pks = new.primary_keys |
| 234 | + if ( |
| 235 | + len(new_pks) == 1 |
| 236 | + and new_pks[0] == "CqIDColumn" |
| 237 | + and get_table_column(old, "CqIDColumn") is None |
| 238 | + and len(old.primary_keys) > 0 |
| 239 | + ): |
| 240 | + changes.append( |
| 241 | + TableColumnChange( |
| 242 | + type=TableColumnChangeType.MOVE_TO_CQ_ONLY, |
| 243 | + ) |
| 244 | + ) |
| 245 | + |
| 246 | + for c in new.columns: |
| 247 | + other_column = get_table_column(old, c.name) |
| 248 | + # A column was added to the table definition |
| 249 | + if other_column is None: |
| 250 | + changes.append( |
| 251 | + TableColumnChange( |
| 252 | + type=TableColumnChangeType.ADD, |
| 253 | + column_name=c.name, |
| 254 | + current=c, |
| 255 | + previous=None, |
| 256 | + ) |
| 257 | + ) |
| 258 | + continue |
| 259 | + |
| 260 | + # Column type or options (e.g. PK, Not Null) changed in the new table definition |
| 261 | + if ( |
| 262 | + c.type != other_column.type |
| 263 | + or c.not_null != other_column.not_null |
| 264 | + or c.primary_key != other_column.primary_key |
| 265 | + ): |
| 266 | + changes.append( |
| 267 | + TableColumnChange( |
| 268 | + type=TableColumnChangeType.UPDATE, |
| 269 | + column_name=c.name, |
| 270 | + current=c, |
| 271 | + previous=other_column, |
| 272 | + ) |
| 273 | + ) |
| 274 | + |
| 275 | + # Unique constraint was removed |
| 276 | + if not c.unique and other_column.unique: |
| 277 | + changes.append( |
| 278 | + TableColumnChange( |
| 279 | + type=TableColumnChangeType.REMOVE_UNIQUE_CONSTRAINT, |
| 280 | + column_name=c.name, |
| 281 | + current=c, |
| 282 | + previous=other_column, |
| 283 | + ) |
| 284 | + ) |
| 285 | + |
| 286 | + # A column was removed from the table definition |
| 287 | + for c in old.columns: |
| 288 | + if get_table_column(new, c.name) is None: |
| 289 | + changes.append( |
| 290 | + TableColumnChange( |
| 291 | + type=TableColumnChangeType.REMOVE, |
| 292 | + column_name=c.name, |
| 293 | + current=None, |
| 294 | + previous=c, |
| 295 | + ) |
| 296 | + ) |
| 297 | + |
| 298 | + return changes |
| 299 | + |
| 300 | + |
| 301 | +def get_table_column(table: Table, column_name: str) -> Optional[Column]: |
| 302 | + for c in table.columns: |
| 303 | + if c.name == column_name: |
| 304 | + return c |
| 305 | + return None |
| 306 | + |
| 307 | + |
| 308 | +def flatten_tables_recursive(original_tables: List[Table]) -> List[Table]: |
| 309 | + tables = [] |
| 310 | + for table in original_tables: |
| 311 | + table_copy = Table( |
| 312 | + name=table.name, |
| 313 | + columns=table.columns, |
| 314 | + relations=table.relations, |
| 315 | + title=table.title, |
| 316 | + description=table.description, |
| 317 | + is_incremental=table.is_incremental, |
| 318 | + parent=table.parent, |
| 319 | + ) |
| 320 | + tables.append(table_copy) |
| 321 | + tables.extend(flatten_tables_recursive(table.relations)) |
| 322 | + return tables |
| 323 | + |
| 324 | + |
| 325 | +def flatten_tables(original_tables: List[Table]) -> List[Table]: |
| 326 | + tables = flatten_tables_recursive(original_tables) |
| 327 | + seen = set() |
| 328 | + deduped = [] |
197 | 329 | for table in tables: |
198 | | - flattened.append(table) |
199 | | - flattened.extend(flatten_tables(table.relations)) |
200 | | - return flattened |
| 330 | + if table.name not in seen: |
| 331 | + deduped.append(table) |
| 332 | + seen.add(table.name) |
| 333 | + return deduped |
0 commit comments