@connorbritain/mssql-mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +61 -0
  2. package/dist/audit/AuditLogger.d.ts +25 -0
  3. package/dist/audit/AuditLogger.d.ts.map +1 -0
  4. package/dist/audit/AuditLogger.js +91 -0
  5. package/dist/audit/AuditLogger.js.map +1 -0
  6. package/dist/config/EnvironmentManager.d.ts +36 -0
  7. package/dist/config/EnvironmentManager.d.ts.map +1 -0
  8. package/dist/config/EnvironmentManager.js +185 -0
  9. package/dist/config/EnvironmentManager.js.map +1 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +615 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/tools/CreateIndexTool.d.ts +24 -0
  15. package/dist/tools/CreateIndexTool.d.ts.map +1 -0
  16. package/dist/tools/CreateIndexTool.js +64 -0
  17. package/dist/tools/CreateIndexTool.js.map +1 -0
  18. package/dist/tools/CreateTableTool.d.ts +12 -0
  19. package/dist/tools/CreateTableTool.d.ts.map +1 -0
  20. package/dist/tools/CreateTableTool.js +49 -0
  21. package/dist/tools/CreateTableTool.js.map +1 -0
  22. package/dist/tools/DeleteDataTool.d.ts +56 -0
  23. package/dist/tools/DeleteDataTool.d.ts.map +1 -0
  24. package/dist/tools/DeleteDataTool.js +103 -0
  25. package/dist/tools/DeleteDataTool.js.map +1 -0
  26. package/dist/tools/DescribeTableTool.d.ts +20 -0
  27. package/dist/tools/DescribeTableTool.d.ts.map +1 -0
  28. package/dist/tools/DescribeTableTool.js +34 -0
  29. package/dist/tools/DescribeTableTool.js.map +1 -0
  30. package/dist/tools/DropTableTool.d.ts +12 -0
  31. package/dist/tools/DropTableTool.d.ts.map +1 -0
  32. package/dist/tools/DropTableTool.js +37 -0
  33. package/dist/tools/DropTableTool.js.map +1 -0
  34. package/dist/tools/ExplainQueryTool.d.ts +24 -0
  35. package/dist/tools/ExplainQueryTool.d.ts.map +1 -0
  36. package/dist/tools/ExplainQueryTool.js +98 -0
  37. package/dist/tools/ExplainQueryTool.js.map +1 -0
  38. package/dist/tools/InsertDataTool.d.ts +17 -0
  39. package/dist/tools/InsertDataTool.d.ts.map +1 -0
  40. package/dist/tools/InsertDataTool.js +144 -0
  41. package/dist/tools/InsertDataTool.js.map +1 -0
  42. package/dist/tools/ListTableTool.d.ts +18 -0
  43. package/dist/tools/ListTableTool.d.ts.map +1 -0
  44. package/dist/tools/ListTableTool.js +43 -0
  45. package/dist/tools/ListTableTool.js.map +1 -0
  46. package/dist/tools/ProfileTableTool.d.ts +78 -0
  47. package/dist/tools/ProfileTableTool.d.ts.map +1 -0
  48. package/dist/tools/ProfileTableTool.js +372 -0
  49. package/dist/tools/ProfileTableTool.js.map +1 -0
  50. package/dist/tools/ReadDataTool.d.ts +58 -0
  51. package/dist/tools/ReadDataTool.d.ts.map +1 -0
  52. package/dist/tools/ReadDataTool.js +265 -0
  53. package/dist/tools/ReadDataTool.js.map +1 -0
  54. package/dist/tools/RelationshipInspectorTool.d.ts +46 -0
  55. package/dist/tools/RelationshipInspectorTool.d.ts.map +1 -0
  56. package/dist/tools/RelationshipInspectorTool.js +155 -0
  57. package/dist/tools/RelationshipInspectorTool.js.map +1 -0
  58. package/dist/tools/SearchSchemaTool.d.ts +88 -0
  59. package/dist/tools/SearchSchemaTool.d.ts.map +1 -0
  60. package/dist/tools/SearchSchemaTool.js +236 -0
  61. package/dist/tools/SearchSchemaTool.js.map +1 -0
  62. package/dist/tools/TestConnectionTool.d.ts +36 -0
  63. package/dist/tools/TestConnectionTool.d.ts.map +1 -0
  64. package/dist/tools/TestConnectionTool.js +155 -0
  65. package/dist/tools/TestConnectionTool.js.map +1 -0
  66. package/dist/tools/UpdateDataTool.d.ts +61 -0
  67. package/dist/tools/UpdateDataTool.d.ts.map +1 -0
  68. package/dist/tools/UpdateDataTool.js +117 -0
  69. package/dist/tools/UpdateDataTool.js.map +1 -0
  70. package/package.json +42 -0
@@ -0,0 +1,144 @@
1
+ import sql from "mssql";
2
+ export class InsertDataTool {
3
+ constructor() {
4
+ this.name = "insert_data";
5
+ this.description = `Inserts data into an MSSQL Database table. Supports both single record insertion and multiple record insertion using standard SQL INSERT with VALUES clause.
6
+ FORMAT EXAMPLES:
7
+ Single Record Insert:
8
+ {
9
+ "tableName": "Users",
10
+ "data": {
11
+ "name": "John Doe",
12
+ "email": "john@example.com",
13
+ "age": 30,
14
+ "isActive": true,
15
+ "createdDate": "2023-01-15"
16
+ }
17
+ }
18
+ Multiple Records Insert:
19
+ {
20
+ "tableName": "Users",
21
+ "data": [
22
+ {
23
+ "name": "John Doe",
24
+ "email": "john@example.com",
25
+ "age": 30,
26
+ "isActive": true,
27
+ "createdDate": "2023-01-15"
28
+ },
29
+ {
30
+ "name": "Jane Smith",
31
+ "email": "jane@example.com",
32
+ "age": 25,
33
+ "isActive": false,
34
+ "createdDate": "2023-01-16"
35
+ }
36
+ ]
37
+ }
38
+ GENERATED SQL FORMAT:
39
+ - Single: INSERT INTO table (col1, col2) VALUES (@param1, @param2)
40
+ - Multiple: INSERT INTO table (col1, col2) VALUES (@param1, @param2), (@param3, @param4), ...
41
+ IMPORTANT RULES:
42
+ - For single record: Use a single object for the 'data' field
43
+ - For multiple records: Use an array of objects for the 'data' field
44
+ - All objects in array must have identical column names
45
+ - Column names must match the actual database table columns exactly
46
+ - Values should match the expected data types (string, number, boolean, date)
47
+ - Use proper date format for date columns (YYYY-MM-DD or ISO format)`;
48
+ this.inputSchema = {
49
+ type: "object",
50
+ properties: {
51
+ tableName: {
52
+ type: "string",
53
+ description: "Name of the table to insert data into"
54
+ },
55
+ data: {
56
+ oneOf: [
57
+ {
58
+ type: "object",
59
+ description: "Single record data object with column names as keys and values as the data to insert. Example: {\"name\": \"John\", \"age\": 30}"
60
+ },
61
+ {
62
+ type: "array",
63
+ items: { type: "object" },
64
+ description: "Array of data objects for multiple record insertion. Each object must have identical column structure. Example: [{\"name\": \"John\", \"age\": 30}, {\"name\": \"Jane\", \"age\": 25}]"
65
+ }
66
+ ]
67
+ },
68
+ },
69
+ required: ["tableName", "data"],
70
+ };
71
+ }
72
+ async run(params) {
73
+ try {
74
+ const { tableName, data } = params;
75
+ // Check if data is an array (multiple records) or single object
76
+ const isMultipleRecords = Array.isArray(data);
77
+ const records = isMultipleRecords ? data : [data];
78
+ if (records.length === 0) {
79
+ return {
80
+ success: false,
81
+ message: "No data provided for insertion",
82
+ };
83
+ }
84
+ // Validate that all records have the same columns
85
+ const firstRecordColumns = Object.keys(records[0]).sort();
86
+ for (let i = 1; i < records.length; i++) {
87
+ const currentColumns = Object.keys(records[i]).sort();
88
+ if (JSON.stringify(firstRecordColumns) !== JSON.stringify(currentColumns)) {
89
+ return {
90
+ success: false,
91
+ message: `Column mismatch: Record ${i + 1} has different columns than the first record. Expected columns: [${firstRecordColumns.join(', ')}], but got: [${currentColumns.join(', ')}]`,
92
+ };
93
+ }
94
+ }
95
+ const columns = firstRecordColumns.join(", ");
96
+ const request = new sql.Request();
97
+ if (isMultipleRecords) {
98
+ // Multiple records insert using VALUES clause - works for 1 or more records
99
+ const valueClauses = [];
100
+ records.forEach((record, recordIndex) => {
101
+ const valueParams = firstRecordColumns
102
+ .map((column, columnIndex) => `@value${recordIndex}_${columnIndex}`)
103
+ .join(", ");
104
+ valueClauses.push(`(${valueParams})`);
105
+ // Add parameters for this record
106
+ firstRecordColumns.forEach((column, columnIndex) => {
107
+ request.input(`value${recordIndex}_${columnIndex}`, record[column]);
108
+ });
109
+ });
110
+ const query = `INSERT INTO ${tableName} (${columns}) VALUES ${valueClauses.join(", ")}`;
111
+ await request.query(query);
112
+ return {
113
+ success: true,
114
+ message: `Successfully inserted ${records.length} record${records.length > 1 ? 's' : ''} into ${tableName}`,
115
+ recordsInserted: records.length,
116
+ };
117
+ }
118
+ else {
119
+ // Single record insert (when data is passed as single object)
120
+ const values = firstRecordColumns
121
+ .map((column, index) => `@value${index}`)
122
+ .join(", ");
123
+ firstRecordColumns.forEach((column, index) => {
124
+ request.input(`value${index}`, records[0][column]);
125
+ });
126
+ const query = `INSERT INTO ${tableName} (${columns}) VALUES (${values})`;
127
+ await request.query(query);
128
+ return {
129
+ success: true,
130
+ message: `Successfully inserted 1 record into ${tableName}`,
131
+ recordsInserted: 1,
132
+ };
133
+ }
134
+ }
135
+ catch (error) {
136
+ console.error("Error inserting data:", error);
137
+ return {
138
+ success: false,
139
+ message: `Failed to insert data: ${error}`,
140
+ };
141
+ }
142
+ }
143
+ }
144
+ //# sourceMappingURL=InsertDataTool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InsertDataTool.js","sourceRoot":"","sources":["../../src/tools/InsertDataTool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import sql from "mssql";
2
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
3
+ export declare class ListTableTool implements Tool {
4
+ [key: string]: any;
5
+ name: string;
6
+ description: string;
7
+ inputSchema: any;
8
+ run(params: any): Promise<{
9
+ success: boolean;
10
+ message: string;
11
+ items: sql.IRecordSet<any>;
12
+ } | {
13
+ success: boolean;
14
+ message: string;
15
+ items?: undefined;
16
+ }>;
17
+ }
18
+ //# sourceMappingURL=ListTableTool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListTableTool.d.ts","sourceRoot":"","sources":["../../src/tools/ListTableTool.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,OAAO,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAE1D,qBAAa,aAAc,YAAW,IAAI;IACxC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;IACnB,IAAI,SAAgB;IACpB,WAAW,SAA2E;IACtF,WAAW,EAaN,GAAG,CAAC;IAEH,GAAG,CAAC,MAAM,EAAE,GAAG;;;;;;;;;CAoBtB"}
@@ -0,0 +1,43 @@
1
+ import sql from "mssql";
2
+ export class ListTableTool {
3
+ constructor() {
4
+ this.name = "list_table";
5
+ this.description = "Lists tables in an MSSQL Database, or list tables in specific schemas";
6
+ this.inputSchema = {
7
+ type: "object",
8
+ properties: {
9
+ parameters: {
10
+ type: "array",
11
+ description: "Schemas to filter by (optional)",
12
+ items: {
13
+ type: "string"
14
+ },
15
+ minItems: 0
16
+ },
17
+ },
18
+ required: [],
19
+ };
20
+ }
21
+ async run(params) {
22
+ try {
23
+ const { parameters } = params;
24
+ const request = new sql.Request();
25
+ const schemaFilter = parameters && parameters.length > 0 ? `AND TABLE_SCHEMA IN (${parameters.map((p) => `'${p}'`).join(", ")})` : "";
26
+ const query = `SELECT TABLE_SCHEMA + '.' + TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' ${schemaFilter} ORDER BY TABLE_SCHEMA, TABLE_NAME`;
27
+ const result = await request.query(query);
28
+ return {
29
+ success: true,
30
+ message: `List tables executed successfully`,
31
+ items: result.recordset,
32
+ };
33
+ }
34
+ catch (error) {
35
+ console.error("Error listing tables:", error);
36
+ return {
37
+ success: false,
38
+ message: `Failed to list tables: ${error}`,
39
+ };
40
+ }
41
+ }
42
+ }
43
+ //# sourceMappingURL=ListTableTool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListTableTool.js","sourceRoot":"","sources":["../../src/tools/ListTableTool.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,OAAO,CAAC;AAGxB,MAAM,OAAO,aAAa;IAA1B;QAEE,SAAI,GAAG,YAAY,CAAC;QACpB,gBAAW,GAAG,uEAAuE,CAAC;QACtF,gBAAW,GAAG;YACZ,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,UAAU,EAAE;oBACV,IAAI,EAAE,OAAO;oBACb,WAAW,EAAE,iCAAiC;oBAC9C,KAAK,EAAE;wBACL,IAAI,EAAE,QAAQ;qBACf;oBACD,QAAQ,EAAE,CAAC;iBACZ;aACF;YACD,QAAQ,EAAE,EAAE;SACN,CAAC;IAsBX,CAAC;IApBC,KAAK,CAAC,GAAG,CAAC,MAAW;QACnB,IAAI,CAAC;YACH,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;YAC9B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,YAAY,GAAG,UAAU,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,wBAAwB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9I,MAAM,KAAK,GAAG,yGAAyG,YAAY,oCAAoC,CAAC;YACxK,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1C,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,mCAAmC;gBAC5C,KAAK,EAAE,MAAM,CAAC,SAAS;aACxB,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,KAAK,CAAC,CAAC;YAC9C,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,0BAA0B,KAAK,EAAE;aAC3C,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,78 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ type ProfileParams = {
3
+ tableName: string;
4
+ schemaName?: string;
5
+ sampleSize?: number;
6
+ includeDistributions?: boolean;
7
+ topValuesLimit?: number;
8
+ columnsToProfile?: string[];
9
+ includeSamples?: boolean;
10
+ };
11
+ type NumericStats = {
12
+ min: number;
13
+ max: number;
14
+ avg: number;
15
+ median?: number;
16
+ p90?: number;
17
+ };
18
+ type StringStats = {
19
+ minLength: number;
20
+ maxLength: number;
21
+ avgLength: number;
22
+ emptyCount: number;
23
+ };
24
+ type DateStats = {
25
+ earliest: string;
26
+ latest: string;
27
+ range: string;
28
+ };
29
+ type TopValue = {
30
+ value: any;
31
+ count: number;
32
+ percentage: number;
33
+ };
34
+ type ColumnProfile = {
35
+ columnName: string;
36
+ dataType: string;
37
+ isNullable: boolean;
38
+ nullCount: number;
39
+ nullPercentage: number;
40
+ distinctCount: number;
41
+ cardinality: "unique" | "high" | "medium" | "low";
42
+ numericStats?: NumericStats;
43
+ stringStats?: StringStats;
44
+ dateStats?: DateStats;
45
+ topValues?: TopValue[];
46
+ };
47
+ type ProfileResult = {
48
+ success: boolean;
49
+ message?: string;
50
+ tableName?: string;
51
+ schemaName?: string;
52
+ rowCount?: number;
53
+ columnCount?: number;
54
+ sampleSize?: number;
55
+ columns?: ColumnProfile[];
56
+ samples?: Record<string, unknown>[];
57
+ };
58
+ export declare class ProfileTableTool implements Tool {
59
+ [key: string]: any;
60
+ name: string;
61
+ description: string;
62
+ inputSchema: any;
63
+ private static readonly SKIP_TYPES;
64
+ private static readonly NUMERIC_TYPES;
65
+ private static readonly STRING_TYPES;
66
+ private static readonly DATE_TYPES;
67
+ private normalizeLimit;
68
+ private classifyCardinality;
69
+ private formatDateRange;
70
+ private escapeIdentifier;
71
+ private isNumericType;
72
+ private isStringType;
73
+ private isDateType;
74
+ private shouldSkipType;
75
+ run(params: ProfileParams): Promise<ProfileResult>;
76
+ }
77
+ export {};
78
+ //# sourceMappingURL=ProfileTableTool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProfileTableTool.d.ts","sourceRoot":"","sources":["../../src/tools/ProfileTableTool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,372 @@
1
+ import sql from "mssql";
2
+ const clampEnvInt = (value, fallback, min, max) => {
3
+ if (!value) {
4
+ return fallback;
5
+ }
6
+ const parsed = parseInt(value, 10);
7
+ if (Number.isNaN(parsed)) {
8
+ return fallback;
9
+ }
10
+ return Math.min(Math.max(parsed, min), max);
11
+ };
12
+ const DEFAULT_SAMPLE_SIZE = clampEnvInt(process.env.PROFILE_SAMPLE_SIZE_DEFAULT, 50, 1, 1000);
13
+ const SAMPLE_RETURN_LIMIT = clampEnvInt(process.env.PROFILE_SAMPLE_RETURN_LIMIT, 10, 1, 100);
14
+ export class ProfileTableTool {
15
+ constructor() {
16
+ this.name = "profile_table";
17
+ this.description = "Profiles a table by analyzing column statistics, data distributions, and sample records. Returns metadata, cardinality info, null counts, and representative samples for each column.";
18
+ this.inputSchema = {
19
+ type: "object",
20
+ properties: {
21
+ tableName: {
22
+ type: "string",
23
+ description: "Name of table to profile (schema.table or table)",
24
+ },
25
+ schemaName: {
26
+ type: "string",
27
+ description: "Explicit schema (defaults to 'dbo')",
28
+ },
29
+ sampleSize: {
30
+ type: "number",
31
+ description: "Number of sample rows (default 100, max 1000)",
32
+ },
33
+ includeSamples: {
34
+ type: "boolean",
35
+ description: "Return sampled rows used for profiling (default false)",
36
+ },
37
+ includeDistributions: {
38
+ type: "boolean",
39
+ description: "Include top value frequencies (default true)",
40
+ },
41
+ topValuesLimit: {
42
+ type: "number",
43
+ description: "Max distinct values per column (default 10, max 50)",
44
+ },
45
+ columnsToProfile: {
46
+ type: "array",
47
+ items: { type: "string" },
48
+ description: "Specific columns to profile (default: all)",
49
+ },
50
+ },
51
+ required: ["tableName"],
52
+ };
53
+ }
54
+ normalizeLimit(value, defaultVal, max) {
55
+ if (typeof value !== "number" || Number.isNaN(value) || value <= 0) {
56
+ return defaultVal;
57
+ }
58
+ return Math.min(Math.floor(value), max);
59
+ }
60
+ classifyCardinality(distinctCount, rowCount) {
61
+ if (rowCount === 0)
62
+ return "low";
63
+ const ratio = distinctCount / rowCount;
64
+ if (ratio > 0.95)
65
+ return "unique";
66
+ if (ratio > 0.5)
67
+ return "high";
68
+ if (ratio > 0.1)
69
+ return "medium";
70
+ return "low";
71
+ }
72
+ formatDateRange(earliest, latest) {
73
+ const diffMs = latest.getTime() - earliest.getTime();
74
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
75
+ if (diffDays < 1)
76
+ return "less than 1 day";
77
+ if (diffDays < 30)
78
+ return `${diffDays} day${diffDays > 1 ? "s" : ""}`;
79
+ const diffMonths = Math.floor(diffDays / 30);
80
+ if (diffMonths < 12)
81
+ return `${diffMonths} month${diffMonths > 1 ? "s" : ""}`;
82
+ const years = Math.floor(diffMonths / 12);
83
+ const remainingMonths = diffMonths % 12;
84
+ if (remainingMonths === 0)
85
+ return `${years} year${years > 1 ? "s" : ""}`;
86
+ return `${years} year${years > 1 ? "s" : ""}, ${remainingMonths} month${remainingMonths > 1 ? "s" : ""}`;
87
+ }
88
+ escapeIdentifier(name) {
89
+ return `[${name.replace(/\]/g, "]]")}]`;
90
+ }
91
+ isNumericType(dataType) {
92
+ return ProfileTableTool.NUMERIC_TYPES.includes(dataType.toLowerCase());
93
+ }
94
+ isStringType(dataType) {
95
+ return ProfileTableTool.STRING_TYPES.includes(dataType.toLowerCase());
96
+ }
97
+ isDateType(dataType) {
98
+ return ProfileTableTool.DATE_TYPES.includes(dataType.toLowerCase());
99
+ }
100
+ shouldSkipType(dataType) {
101
+ return ProfileTableTool.SKIP_TYPES.includes(dataType.toLowerCase());
102
+ }
103
+ async run(params) {
104
+ try {
105
+ const tableName = params.tableName?.trim();
106
+ if (!tableName) {
107
+ return { success: false, message: "tableName is required." };
108
+ }
109
+ const schemaName = params.schemaName?.trim() || "dbo";
110
+ const sampleSize = this.normalizeLimit(params.sampleSize, DEFAULT_SAMPLE_SIZE, 1000);
111
+ const includeDistributions = params.includeDistributions !== false;
112
+ const includeSamples = params.includeSamples === true;
113
+ const topValuesLimit = this.normalizeLimit(params.topValuesLimit, 10, 50);
114
+ const columnsToProfile = params.columnsToProfile?.map((c) => c.trim()).filter(Boolean);
115
+ // 1. Validate table exists and get columns
116
+ const metaRequest = new sql.Request();
117
+ metaRequest.input("schemaName", sql.NVarChar, schemaName);
118
+ metaRequest.input("tableName", sql.NVarChar, tableName);
119
+ const metaResult = await metaRequest.query(`
120
+ SELECT
121
+ c.COLUMN_NAME AS columnName,
122
+ c.DATA_TYPE AS dataType,
123
+ CASE WHEN c.IS_NULLABLE = 'YES' THEN 1 ELSE 0 END AS isNullable
124
+ FROM INFORMATION_SCHEMA.COLUMNS c
125
+ INNER JOIN INFORMATION_SCHEMA.TABLES t
126
+ ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME
127
+ WHERE c.TABLE_SCHEMA = @schemaName AND c.TABLE_NAME = @tableName
128
+ ORDER BY c.ORDINAL_POSITION
129
+ `);
130
+ if (!metaResult.recordset.length) {
131
+ return {
132
+ success: false,
133
+ message: `Table [${schemaName}].[${tableName}] not found or has no columns.`,
134
+ };
135
+ }
136
+ // Filter columns if requested
137
+ let columns = [...metaResult.recordset];
138
+ if (columnsToProfile && columnsToProfile.length) {
139
+ const requested = new Set(columnsToProfile.map((c) => c.toLowerCase()));
140
+ columns = columns.filter((c) => requested.has(c.columnName.toLowerCase()));
141
+ if (!columns.length) {
142
+ return {
143
+ success: false,
144
+ message: `None of the requested columns exist in [${schemaName}].[${tableName}].`,
145
+ };
146
+ }
147
+ }
148
+ // Filter out binary/blob types
149
+ columns = columns.filter((c) => !this.shouldSkipType(c.dataType));
150
+ // 2. Get total row count
151
+ const countRequest = new sql.Request();
152
+ const fqTable = `${this.escapeIdentifier(schemaName)}.${this.escapeIdentifier(tableName)}`;
153
+ const countResult = await countRequest.query(`SELECT COUNT(*) AS cnt FROM ${fqTable}`);
154
+ const rowCount = countResult.recordset[0]?.cnt ?? 0;
155
+ let sampleRows;
156
+ if (rowCount === 0) {
157
+ return {
158
+ success: true,
159
+ tableName,
160
+ schemaName,
161
+ rowCount: 0,
162
+ columnCount: columns.length,
163
+ sampleSize: 0,
164
+ columns: columns.map((c) => ({
165
+ columnName: c.columnName,
166
+ dataType: c.dataType,
167
+ isNullable: Boolean(c.isNullable),
168
+ nullCount: 0,
169
+ nullPercentage: 0,
170
+ distinctCount: 0,
171
+ cardinality: "low",
172
+ })),
173
+ };
174
+ }
175
+ if (includeSamples) {
176
+ const sampleRequest = new sql.Request();
177
+ const sampleQuery = `
178
+ SELECT TOP (${sampleSize}) *
179
+ FROM ${fqTable}
180
+ ORDER BY NEWID()
181
+ `;
182
+ const sampleResult = await sampleRequest.query(sampleQuery);
183
+ sampleRows = (sampleResult.recordset ?? []).slice(0, SAMPLE_RETURN_LIMIT);
184
+ }
185
+ // 3. Profile each column
186
+ const columnProfiles = [];
187
+ for (const col of columns) {
188
+ const colName = this.escapeIdentifier(col.columnName);
189
+ const profile = {
190
+ columnName: col.columnName,
191
+ dataType: col.dataType,
192
+ isNullable: Boolean(col.isNullable),
193
+ nullCount: 0,
194
+ nullPercentage: 0,
195
+ distinctCount: 0,
196
+ cardinality: "low",
197
+ };
198
+ // Base stats: null count and distinct count
199
+ const baseRequest = new sql.Request();
200
+ const baseResult = await baseRequest.query(`
201
+ SELECT
202
+ SUM(CASE WHEN ${colName} IS NULL THEN 1 ELSE 0 END) AS nullCount,
203
+ COUNT(DISTINCT ${colName}) AS distinctCount
204
+ FROM ${fqTable}
205
+ `);
206
+ const baseRow = baseResult.recordset[0];
207
+ profile.nullCount = baseRow?.nullCount ?? 0;
208
+ profile.nullPercentage = rowCount > 0 ? Number(((profile.nullCount / rowCount) * 100).toFixed(2)) : 0;
209
+ profile.distinctCount = baseRow?.distinctCount ?? 0;
210
+ profile.cardinality = this.classifyCardinality(profile.distinctCount, rowCount);
211
+ // Type-specific stats
212
+ if (this.isNumericType(col.dataType)) {
213
+ const numRequest = new sql.Request();
214
+ const numResult = await numRequest.query(`
215
+ SELECT
216
+ MIN(${colName}) AS minVal,
217
+ MAX(${colName}) AS maxVal,
218
+ AVG(CAST(${colName} AS FLOAT)) AS avgVal
219
+ FROM ${fqTable}
220
+ WHERE ${colName} IS NOT NULL
221
+ `);
222
+ const numRow = numResult.recordset[0];
223
+ if (numRow && numRow.minVal !== null) {
224
+ profile.numericStats = {
225
+ min: numRow.minVal,
226
+ max: numRow.maxVal,
227
+ avg: Number(Number(numRow.avgVal).toFixed(4)),
228
+ };
229
+ const percentileRequest = new sql.Request();
230
+ const percentileResult = await percentileRequest.query(`
231
+ SELECT TOP 1
232
+ PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ${colName}) OVER () AS medianVal,
233
+ PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY ${colName}) OVER () AS p90Val
234
+ FROM ${fqTable}
235
+ WHERE ${colName} IS NOT NULL
236
+ `);
237
+ const percentileRow = percentileResult.recordset[0];
238
+ if (percentileRow) {
239
+ if (percentileRow.medianVal !== null && percentileRow.medianVal !== undefined) {
240
+ profile.numericStats.median = Number(Number(percentileRow.medianVal).toFixed(4));
241
+ }
242
+ if (percentileRow.p90Val !== null && percentileRow.p90Val !== undefined) {
243
+ profile.numericStats.p90 = Number(Number(percentileRow.p90Val).toFixed(4));
244
+ }
245
+ }
246
+ }
247
+ }
248
+ else if (this.isStringType(col.dataType)) {
249
+ const strRequest = new sql.Request();
250
+ const strResult = await strRequest.query(`
251
+ SELECT
252
+ MIN(LEN(${colName})) AS minLength,
253
+ MAX(LEN(${colName})) AS maxLength,
254
+ AVG(CAST(LEN(${colName}) AS FLOAT)) AS avgLength,
255
+ SUM(CASE WHEN ${colName} = '' THEN 1 ELSE 0 END) AS emptyCount
256
+ FROM ${fqTable}
257
+ WHERE ${colName} IS NOT NULL
258
+ `);
259
+ const strRow = strResult.recordset[0];
260
+ if (strRow && strRow.minLength !== null) {
261
+ profile.stringStats = {
262
+ minLength: strRow.minLength,
263
+ maxLength: strRow.maxLength,
264
+ avgLength: Number(Number(strRow.avgLength).toFixed(2)),
265
+ emptyCount: strRow.emptyCount ?? 0,
266
+ };
267
+ }
268
+ }
269
+ else if (this.isDateType(col.dataType)) {
270
+ const dateRequest = new sql.Request();
271
+ const dateResult = await dateRequest.query(`
272
+ SELECT
273
+ MIN(${colName}) AS earliest,
274
+ MAX(${colName}) AS latest
275
+ FROM ${fqTable}
276
+ WHERE ${colName} IS NOT NULL
277
+ `);
278
+ const dateRow = dateResult.recordset[0];
279
+ if (dateRow && dateRow.earliest !== null) {
280
+ const earliest = new Date(dateRow.earliest);
281
+ const latest = new Date(dateRow.latest);
282
+ profile.dateStats = {
283
+ earliest: earliest.toISOString(),
284
+ latest: latest.toISOString(),
285
+ range: this.formatDateRange(earliest, latest),
286
+ };
287
+ }
288
+ }
289
+ // Top values distribution
290
+ if (includeDistributions && profile.distinctCount > 0 && profile.distinctCount <= rowCount) {
291
+ const topRequest = new sql.Request();
292
+ topRequest.input("topLimit", sql.Int, topValuesLimit);
293
+ const topResult = await topRequest.query(`
294
+ SELECT TOP (@topLimit)
295
+ ${colName} AS value,
296
+ COUNT(*) AS cnt
297
+ FROM ${fqTable}
298
+ WHERE ${colName} IS NOT NULL
299
+ GROUP BY ${colName}
300
+ ORDER BY cnt DESC
301
+ `);
302
+ if (topResult.recordset.length) {
303
+ profile.topValues = topResult.recordset.map((r) => ({
304
+ value: r.value,
305
+ count: r.cnt,
306
+ percentage: Number(((r.cnt / rowCount) * 100).toFixed(2)),
307
+ }));
308
+ }
309
+ }
310
+ columnProfiles.push(profile);
311
+ }
312
+ return {
313
+ success: true,
314
+ tableName,
315
+ schemaName,
316
+ rowCount,
317
+ columnCount: columnProfiles.length,
318
+ sampleSize: includeSamples ? sampleRows?.length ?? 0 : sampleSize,
319
+ columns: columnProfiles,
320
+ samples: includeSamples ? sampleRows : undefined,
321
+ };
322
+ }
323
+ catch (error) {
324
+ return {
325
+ success: false,
326
+ message: `Failed to profile table: ${error}`,
327
+ };
328
+ }
329
+ }
330
+ }
331
+ // Binary/blob types to skip
332
+ ProfileTableTool.SKIP_TYPES = [
333
+ "image",
334
+ "varbinary",
335
+ "binary",
336
+ "timestamp",
337
+ "rowversion",
338
+ "sql_variant",
339
+ "xml",
340
+ "geography",
341
+ "geometry",
342
+ "hierarchyid",
343
+ ];
344
+ ProfileTableTool.NUMERIC_TYPES = [
345
+ "int",
346
+ "bigint",
347
+ "smallint",
348
+ "tinyint",
349
+ "decimal",
350
+ "numeric",
351
+ "float",
352
+ "real",
353
+ "money",
354
+ "smallmoney",
355
+ ];
356
+ ProfileTableTool.STRING_TYPES = [
357
+ "char",
358
+ "varchar",
359
+ "nchar",
360
+ "nvarchar",
361
+ "text",
362
+ "ntext",
363
+ ];
364
+ ProfileTableTool.DATE_TYPES = [
365
+ "date",
366
+ "datetime",
367
+ "datetime2",
368
+ "smalldatetime",
369
+ "datetimeoffset",
370
+ "time",
371
+ ];
372
+ //# sourceMappingURL=ProfileTableTool.js.map