Skip to content

Commit 9f9b7a2

Browse files
pythonHuangclaude
andcommitted
fix: SQLServer/PG数据库对象与字段重复-跨schema同名对象用schema.名限定唯一化+字段/参数查询按schema精确过滤(修表列表重复+右侧字段重复)
Co-Authored-By: Claude Code <noreply@anthropic.com>
1 parent cc71c6e commit 9f9b7a2

1 file changed

Lines changed: 61 additions & 22 deletions

File tree

‎Juggle.Application/Services/Impl/DataSourceService.cs‎

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -109,48 +109,57 @@ public async Task<DbObjectsResult> GetDbObjectsAsync(string dataSourceName)
109109
}
110110
case "postgresql" or "postgres":
111111
{
112+
var pgTables = new List<(string Schema, string Name, bool IsView)>();
112113
await using (var cmd = conn.CreateCommand())
113114
{
114-
cmd.CommandText = "SELECT table_name, table_type FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog','information_schema') ORDER BY table_name";
115+
cmd.CommandText = "SELECT table_schema, table_name, table_type FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog','information_schema') ORDER BY table_schema, table_name";
115116
await using var reader = await cmd.ExecuteReaderAsync();
116117
while (await reader.ReadAsync())
117-
{
118-
var name = reader.GetValue(0)?.ToString() ?? "";
119-
var type = reader.GetValue(1)?.ToString() ?? "";
120-
if (type == "VIEW") result.Views.Add(new DbObjectItem { Name = name });
121-
else result.Tables.Add(new DbObjectItem { Name = name });
122-
}
118+
pgTables.Add((
119+
reader.GetValue(0)?.ToString() ?? "",
120+
reader.GetValue(1)?.ToString() ?? "",
121+
string.Equals(reader.GetValue(2)?.ToString(), "VIEW", StringComparison.OrdinalIgnoreCase)));
123122
}
123+
AddWithSchemaQualify(pgTables.Where(t => !t.IsView).Select(t => (t.Schema, t.Name)), result.Tables);
124+
AddWithSchemaQualify(pgTables.Where(t => t.IsView).Select(t => (t.Schema, t.Name)), result.Views);
125+
126+
var pgProcs = new List<(string Schema, string Name)>();
124127
await using (var cmd = conn.CreateCommand())
125128
{
126-
cmd.CommandText = "SELECT routine_name FROM information_schema.routines WHERE routine_type = 'PROCEDURE' AND routine_schema NOT IN ('pg_catalog','information_schema') ORDER BY routine_name";
129+
cmd.CommandText = "SELECT routine_schema, routine_name FROM information_schema.routines WHERE routine_type = 'PROCEDURE' AND routine_schema NOT IN ('pg_catalog','information_schema') ORDER BY routine_schema, routine_name";
127130
await using var reader = await cmd.ExecuteReaderAsync();
128131
while (await reader.ReadAsync())
129-
result.Procedures.Add(new DbObjectItem { Name = reader.GetValue(0)?.ToString() ?? "" });
132+
pgProcs.Add((reader.GetValue(0)?.ToString() ?? "", reader.GetValue(1)?.ToString() ?? ""));
130133
}
134+
AddWithSchemaQualify(pgProcs, result.Procedures);
131135
break;
132136
}
133137
case "sqlserver" or "mssql":
134138
{
139+
// INFORMATION_SCHEMA.TABLES 含所有 schema,同名表跨 schema 时用 schema.名 限定
140+
var msTables = new List<(string Schema, string Name, bool IsView)>();
135141
await using (var cmd = conn.CreateCommand())
136142
{
137-
cmd.CommandText = "SELECT TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME";
143+
cmd.CommandText = "SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA NOT IN ('sys','INFORMATION_SCHEMA') ORDER BY TABLE_SCHEMA, TABLE_NAME";
138144
await using var reader = await cmd.ExecuteReaderAsync();
139145
while (await reader.ReadAsync())
140-
{
141-
var name = reader.GetValue(0)?.ToString() ?? "";
142-
var type = reader.GetValue(1)?.ToString() ?? "";
143-
if (type == "VIEW") result.Views.Add(new DbObjectItem { Name = name });
144-
else result.Tables.Add(new DbObjectItem { Name = name });
145-
}
146+
msTables.Add((
147+
reader.GetValue(0)?.ToString() ?? "",
148+
reader.GetValue(1)?.ToString() ?? "",
149+
string.Equals(reader.GetValue(2)?.ToString(), "VIEW", StringComparison.OrdinalIgnoreCase)));
146150
}
151+
AddWithSchemaQualify(msTables.Where(t => !t.IsView).Select(t => (t.Schema, t.Name)), result.Tables);
152+
AddWithSchemaQualify(msTables.Where(t => t.IsView).Select(t => (t.Schema, t.Name)), result.Views);
153+
154+
var msProcs = new List<(string Schema, string Name)>();
147155
await using (var cmd = conn.CreateCommand())
148156
{
149-
cmd.CommandText = "SELECT name FROM sys.procedures ORDER BY name";
157+
cmd.CommandText = "SELECT s.name, p.name FROM sys.procedures p JOIN sys.schemas s ON p.schema_id = s.schema_id WHERE s.name NOT IN ('sys') ORDER BY s.name, p.name";
150158
await using var reader = await cmd.ExecuteReaderAsync();
151159
while (await reader.ReadAsync())
152-
result.Procedures.Add(new DbObjectItem { Name = reader.GetValue(0)?.ToString() ?? "" });
160+
msProcs.Add((reader.GetValue(0)?.ToString() ?? "", reader.GetValue(1)?.ToString() ?? ""));
153161
}
162+
AddWithSchemaQualify(msProcs, result.Procedures);
154163
break;
155164
}
156165
case "oracle":
@@ -246,12 +255,13 @@ public async Task<List<DbColumnItem>> GetColumnsAsync(string dataSourceName, str
246255
}
247256
case "postgresql" or "postgres":
248257
{
258+
var (pgSchema, pgTable) = SplitSchemaName(tableName, "public");
249259
await using var cmd = conn.CreateCommand();
250260
cmd.CommandText = $"""
251261
SELECT c.column_name, c.data_type, c.is_nullable, c.column_default,
252262
pg_catalog.col_description(format('%I.%I', c.table_schema, c.table_name)::regclass, c.ordinal_position) AS comment
253263
FROM information_schema.columns c
254-
WHERE c.table_schema NOT IN ('pg_catalog', 'information_schema') AND c.table_name = '{safeName}'
264+
WHERE c.table_schema = '{pgSchema}' AND c.table_name = '{pgTable}'
255265
ORDER BY c.ordinal_position
256266
""";
257267
await using var reader = await cmd.ExecuteReaderAsync();
@@ -270,6 +280,8 @@ ORDER BY c.ordinal_position
270280
}
271281
case "sqlserver" or "mssql":
272282
{
283+
// 按 schema+表名 精确过滤,避免同名表跨 schema 时字段混合重复
284+
var (msSchema, msTable) = SplitSchemaName(tableName, "dbo");
273285
await using var cmd = conn.CreateCommand();
274286
cmd.CommandText = $"""
275287
SELECT c.COLUMN_NAME, c.DATA_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT,
@@ -279,7 +291,7 @@ LEFT JOIN sys.extended_properties ep
279291
ON ep.major_id = OBJECT_ID(c.TABLE_SCHEMA + '.' + c.TABLE_NAME)
280292
AND ep.minor_id = c.ORDINAL_POSITION
281293
AND ep.class = 1 AND ep.name = 'MS_Description'
282-
WHERE c.TABLE_NAME = '{safeName}'
294+
WHERE c.TABLE_SCHEMA = '{msSchema}' AND c.TABLE_NAME = '{msTable}'
283295
ORDER BY c.ORDINAL_POSITION
284296
""";
285297
await using var reader = await cmd.ExecuteReaderAsync();
@@ -362,11 +374,12 @@ ORDER BY ORDINAL_POSITION
362374
}
363375
case "postgresql" or "postgres":
364376
{
377+
var (pgSchema, pgProc) = SplitSchemaName(procName, "public");
365378
await using var cmd = conn.CreateCommand();
366379
cmd.CommandText = $"""
367380
SELECT parameter_name, data_type, parameter_mode
368381
FROM information_schema.parameters
369-
WHERE specific_name = '{safeName}'
382+
WHERE routine_schema = '{pgSchema}' AND specific_name = '{pgProc}'
370383
ORDER BY ordinal_position
371384
""";
372385
await using var reader = await cmd.ExecuteReaderAsync();
@@ -383,15 +396,18 @@ ORDER BY ordinal_position
383396
}
384397
case "sqlserver" or "mssql":
385398
{
399+
// 按 schema+存储过程名 精确过滤,避免跨 schema 同名过程参数混合
400+
var (msSchema, msProc) = SplitSchemaName(procName, "dbo");
386401
await using var cmd = conn.CreateCommand();
387402
cmd.CommandText = $"""
388403
SELECT p.name, t.name AS data_type,
389404
CASE WHEN p.has_default_value = 1 THEN CAST(p.default_value AS NVARCHAR(200)) ELSE NULL END AS default_value,
390405
CASE WHEN p.is_output = 1 THEN 'OUT' ELSE 'IN' END AS mode
391406
FROM sys.procedures sp
407+
JOIN sys.schemas s ON sp.schema_id = s.schema_id
392408
JOIN sys.parameters p ON sp.object_id = p.object_id
393409
JOIN sys.types t ON p.user_type_id = t.user_type_id
394-
WHERE sp.name = '{safeName}' AND p.parameter_id > 0
410+
WHERE s.name = '{msSchema}' AND sp.name = '{msProc}' AND p.parameter_id > 0
395411
ORDER BY p.parameter_id
396412
""";
397413
await using var reader = await cmd.ExecuteReaderAsync();
@@ -538,6 +554,29 @@ private static string RenderTemplate(string sql, Dictionary<string, string?>? va
538554
return vars != null && vars.TryGetValue(varName, out var v) && v != null ? v : "";
539555
});
540556
}
557+
558+
/// <summary>
559+
/// 写入对象列表:同名对象跨 schema 时用 schema.名 限定,保证列表唯一不重复。
560+
/// </summary>
561+
private static void AddWithSchemaQualify(IEnumerable<(string Schema, string Name)> items, List<DbObjectItem> target)
562+
{
563+
foreach (var group in items.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase))
564+
{
565+
foreach (var item in group)
566+
target.Add(new DbObjectItem
567+
{
568+
Name = group.Count() > 1 ? $"{item.Schema}.{item.Name}" : item.Name
569+
});
570+
}
571+
}
572+
573+
/// <summary>拆分 schema.表名(无 schema 时返回默认 schema)</summary>
574+
private static (string Schema, string Name) SplitSchemaName(string objectName, string defaultSchema)
575+
{
576+
var idx = objectName.LastIndexOf('.');
577+
if (idx <= 0) return (defaultSchema, objectName.Replace("'", "''"));
578+
return (objectName[..idx].Replace("'", "''"), objectName[(idx + 1)..].Replace("'", "''"));
579+
}
541580
}
542581

543582
/// <summary>数据库对象列表(表/视图/存储过程)</summary>

0 commit comments

Comments
 (0)