(cascadingPaginationColumnConfig *CascadingPaginationColumnConfig)
| 262 | } |
| 263 | |
| 264 | func (t *TableSchema) paginationKeyColumn(cascadingPaginationColumnConfig *CascadingPaginationColumnConfig) (*schema.TableColumn, int, error) { |
| 265 | var err error |
| 266 | var paginationKeyColumn *schema.TableColumn |
| 267 | var paginationKeyIndex int |
| 268 | |
| 269 | if paginationColumn, found := cascadingPaginationColumnConfig.PaginationColumnFor(t.Schema, t.Name); found { |
| 270 | // Use per-schema, per-table pagination key from config |
| 271 | paginationKeyColumn, paginationKeyIndex, err = t.findColumnByName(paginationColumn) |
| 272 | } else if len(t.PKColumns) == 1 { |
| 273 | // Use Primary Key |
| 274 | paginationKeyIndex = t.PKColumns[0] |
| 275 | paginationKeyColumn = &t.Columns[paginationKeyIndex] |
| 276 | } else if fallbackColumnName, found := cascadingPaginationColumnConfig.FallbackPaginationColumnName(); found { |
| 277 | // Try fallback from config |
| 278 | paginationKeyColumn, paginationKeyIndex, err = t.findColumnByName(fallbackColumnName) |
| 279 | } else { |
| 280 | // No usable pagination key found |
| 281 | err = NonExistingPaginationKeyError(t.Schema, t.Name) |
| 282 | } |
| 283 | |
| 284 | if paginationKeyColumn != nil { |
| 285 | isNumber := paginationKeyColumn.Type == schema.TYPE_NUMBER || paginationKeyColumn.Type == schema.TYPE_MEDIUM_INT |
| 286 | isBinary := paginationKeyColumn.Type == schema.TYPE_BINARY || |
| 287 | paginationKeyColumn.Type == schema.TYPE_STRING |
| 288 | |
| 289 | if !isNumber && !isBinary { |
| 290 | return nil, -1, NonNumericPaginationKeyError(t.Schema, t.Name, paginationKeyColumn.Name) |
| 291 | } |
| 292 | |
| 293 | // For string types (VARCHAR, CHAR), validate that the collation is binary |
| 294 | // BINARY and VARBINARY types don't have collations and are always binary-safe |
| 295 | // Related PR comment with integration test proof: https://github.com/Shopify/ghostferry/pull/417#discussion_r2619684805 |
| 296 | if paginationKeyColumn.Type == schema.TYPE_STRING && paginationKeyColumn.Collation != "" { |
| 297 | // Binary collations end with "_bin" (e.g., utf8mb4_bin, latin1_bin) |
| 298 | // BINARY type has empty collation and is handled above |
| 299 | if !strings.HasSuffix(paginationKeyColumn.Collation, "_bin") { |
| 300 | return nil, -1, NonBinaryCollationError(t.Schema, t.Name, paginationKeyColumn.Name, paginationKeyColumn.Collation) |
| 301 | } |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | return paginationKeyColumn, paginationKeyIndex, err |
| 306 | } |
| 307 | |
| 308 | // GetPaginationColumn retrieves PaginationKeyColumn |
| 309 | func (t *TableSchema) GetPaginationColumn() *schema.TableColumn { |
no test coverage detected