Skip to content

Commit acaf66f

Browse files
author
lmj
committed
Add transformations module and type mapping reference
1 parent 6eadc4d commit acaf66f

32 files changed

Lines changed: 1875 additions & 0 deletions

docs/MAPPING_REFERENCE.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# 类型映射参考(MySQL/Oracle → PostgreSQL)
2+
3+
> 本表为默认策略摘要,具体行为可通过规则(overrides)与 CLI 预览偏好进行覆盖。
4+
5+
## MySQL → PostgreSQL
6+
7+
- 数值
8+
- `tinyint(1)``boolean`
9+
- `smallint``smallint`
10+
- `mediumint``integer`
11+
- `int`/`integer``integer`
12+
- `bigint``bigint`(unsigned → `numeric(20,0)`
13+
- `decimal(p,s)`/`numeric(p,s)``numeric(p,s)`
14+
- `float``real`
15+
- `double`/`double precision``double precision`
16+
17+
- 字符/文本
18+
- `char(n)``char(n)`
19+
- `varchar(n)``varchar(n)`
20+
- `text`/`tinytext`/`mediumtext`/`longtext``text`
21+
22+
- 二进制
23+
- `binary`/`varbinary`/`blob`/`tinyblob`/`mediumblob`/`longblob``bytea`
24+
25+
- 时间
26+
- `date``date`
27+
- `datetime`/`timestamp``timestamp without time zone`(可在预览中设置 `--timestamp-tz`
28+
- `time``time without time zone`
29+
30+
- 其他
31+
- `json``jsonb`
32+
- `enum`/`set``text`
33+
- `year``integer`
34+
35+
## Oracle → PostgreSQL
36+
37+
- 数值
38+
- `number(p,s)``numeric(p,s)`;当 `s=0``p≤9``integer``p≤18``bigint`;否则 `numeric(p,0)`
39+
40+
- 字符/文本
41+
- `varchar2(n)`/`nvarchar2(n)``varchar(n)`
42+
- `char(n)`/`nchar(n)``char(n)`
43+
- `clob`/`nclob``text`
44+
45+
- 二进制
46+
- `raw`/`blob``bytea`
47+
48+
- 时间与区间
49+
- `date``timestamp without time zone`
50+
- `timestamp with time zone``timestamp with time zone`
51+
- `timestamp``timestamp without time zone`
52+
- `interval year to month``interval year to month`
53+
- `interval day to second[(scale)]``interval day to second[(scale)]`
54+
55+
## 可配置与覆盖
56+
57+
- 规则(overrides):见 `docs/rules/*.yaml` 或使用 `db-syncer transform rules` 生成模板
58+
- 预览偏好:DDL 预览支持 `--timestamp-tz``--binary-as`
59+
- 运行时 SMT:`ApplyTypeMapping` 提供时间/JSON/可变精度 decimal 的标准化
60+

docs/rules/mysql-defaults.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
sourceDb: mysql
2+
overrides:
3+
# Treat tinyint(1) as boolean
4+
- name: tinyint
5+
lengthEquals: 1
6+
target: boolean
7+
8+
# Map unsigned bigint to numeric(20,0)
9+
- name: bigint
10+
unsigned: true
11+
target: numeric(20,0)
12+
13+
# Ensure json maps to jsonb
14+
- name: json
15+
target: jsonb
16+
17+
# Example: normalize datetime to timestamp without time zone (uncomment if desired)
18+
# - name: datetime
19+
# target: timestamp without time zone
20+

docs/rules/oracle-defaults.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
sourceDb: oracle
2+
overrides:
3+
# Map RAW to bytea explicitly
4+
- name: raw
5+
target: bytea
6+
7+
# Example: NUMBER(p,0) small precision to integer (uncomment and adjust as needed)
8+
# - name: number
9+
# precisionEquals: 9
10+
# scaleEquals: 0
11+
# target: integer
12+
13+
# Example: NUMBER(p,0) medium to bigint
14+
# - name: number
15+
# precisionEquals: 18
16+
# scaleEquals: 0
17+
# target: bigint
18+

transformations/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@
5252
<artifactId>slf4j-api</artifactId>
5353
</dependency>
5454

55+
<!-- Jackson (JSON/YAML) for rule loading -->
56+
<dependency>
57+
<groupId>com.fasterxml.jackson.core</groupId>
58+
<artifactId>jackson-databind</artifactId>
59+
</dependency>
60+
<dependency>
61+
<groupId>com.fasterxml.jackson.dataformat</groupId>
62+
<artifactId>jackson-dataformat-yaml</artifactId>
63+
</dependency>
64+
5565
<!-- Testing -->
5666
<dependency>
5767
<groupId>org.junit.jupiter</groupId>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.dbsyncer.transformations.mapper;
2+
3+
/**
4+
* Maps a source column type (name + optional attributes) to a target database column definition.
5+
*/
6+
public interface DatabaseTypeMapper {
7+
8+
/**
9+
* Map a source column to target database column definition.
10+
*
11+
* @param column source column metadata
12+
* @return mapped target column definition
13+
*/
14+
TargetColumn map(SourceColumn column);
15+
}
16+
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.dbsyncer.transformations.mapper;
2+
3+
import java.util.Locale;
4+
5+
/**
6+
* Basic MySQL → PostgreSQL column type mapping.
7+
* Covers the most common numeric, string, datetime, binary and JSON types.
8+
*/
9+
public class MySqlToPostgresTypeMapper implements DatabaseTypeMapper {
10+
11+
@Override
12+
public TargetColumn map(SourceColumn column) {
13+
String typeName = column.getTypeName() != null
14+
? column.getTypeName().toLowerCase(Locale.ROOT)
15+
: "";
16+
String mappedType = switch (typeName) {
17+
case "tinyint" -> mapTinyInt(column);
18+
case "smallint" -> "smallint";
19+
case "mediumint" -> "integer";
20+
case "int", "integer" -> "integer";
21+
case "bigint" -> column.isUnsigned() ? "numeric(20,0)" : "bigint";
22+
case "decimal", "numeric" -> mapDecimal(column);
23+
case "float" -> "real";
24+
case "double" -> "double precision";
25+
26+
case "char" -> buildCharType("char", column.getLength());
27+
case "varchar" -> buildCharType("varchar", column.getLength());
28+
case "text", "tinytext", "mediumtext", "longtext" -> "text";
29+
30+
case "date" -> "date";
31+
case "datetime", "timestamp" -> "timestamp";
32+
case "time" -> "time";
33+
34+
case "blob", "tinyblob", "mediumblob", "longblob", "varbinary", "binary" -> "bytea";
35+
case "json" -> "jsonb";
36+
37+
default -> "text";
38+
};
39+
40+
return TargetColumn.builder()
41+
.name(column.getName())
42+
.typeDefinition(mappedType)
43+
.nullable(column.isNullable())
44+
.build();
45+
}
46+
47+
private String mapTinyInt(SourceColumn column) {
48+
if (column.getLength() != null && column.getLength() == 1) {
49+
return "boolean";
50+
}
51+
return "smallint";
52+
}
53+
54+
private String mapDecimal(SourceColumn column) {
55+
Integer length = column.getLength();
56+
Integer scale = column.getScale();
57+
if (length != null && scale != null) {
58+
return "numeric(" + length + "," + scale + ")";
59+
}
60+
if (length != null) {
61+
return "numeric(" + length + ",0)";
62+
}
63+
return "numeric";
64+
}
65+
66+
private String buildCharType(String base, Integer length) {
67+
if (length == null || length <= 0) {
68+
return base;
69+
}
70+
return base + "(" + length + ")";
71+
}
72+
}
73+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.dbsyncer.transformations.mapper;
2+
3+
import lombok.Builder;
4+
import lombok.Value;
5+
6+
/**
7+
* Simplified representation of a source database column for type mapping.
8+
*/
9+
@Value
10+
@Builder
11+
public class SourceColumn {
12+
13+
String name;
14+
String typeName;
15+
Integer length;
16+
Integer scale;
17+
boolean nullable;
18+
boolean unsigned;
19+
}
20+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.dbsyncer.transformations.mapper;
2+
3+
import lombok.Builder;
4+
import lombok.Value;
5+
6+
/**
7+
* Target database column definition produced by a type mapper.
8+
*/
9+
@Value
10+
@Builder
11+
public class TargetColumn {
12+
13+
String name;
14+
String typeDefinition;
15+
boolean nullable;
16+
}
17+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.dbsyncer.transformations.mapper;
2+
3+
import java.util.HashMap;
4+
import java.util.Locale;
5+
import java.util.Map;
6+
import java.util.Optional;
7+
8+
/**
9+
* Registry for database type mappers keyed by logical source database name.
10+
*/
11+
public class TypeMappingRegistry {
12+
13+
private final Map<String, DatabaseTypeMapper> mappers = new HashMap<>();
14+
15+
public void register(String sourceDb, DatabaseTypeMapper mapper) {
16+
mappers.put(normalizeKey(sourceDb), mapper);
17+
}
18+
19+
public Optional<DatabaseTypeMapper> get(String sourceDb) {
20+
return Optional.ofNullable(mappers.get(normalizeKey(sourceDb)));
21+
}
22+
23+
private String normalizeKey(String key) {
24+
return key == null ? "" : key.toLowerCase(Locale.ROOT);
25+
}
26+
}
27+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.dbsyncer.transformations.schema;
2+
3+
import lombok.Builder;
4+
import lombok.Value;
5+
6+
@Value
7+
@Builder
8+
public class ColumnDefinition {
9+
String name;
10+
String postgresType;
11+
}
12+

0 commit comments

Comments
 (0)