get-objects-lib 0.1.0__tar.gz

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.
@@ -0,0 +1,231 @@
1
+ Metadata-Version: 2.3
2
+ Name: get-objects-lib
3
+ Version: 0.1.0
4
+ Summary: Extrae objetos referenciados en scripts T-SQL
5
+ Keywords: sql,tsql,sqlglot
6
+ Author: Ivan-Rosales
7
+ Author-email: 61328655+Ivan-Rosales@users.noreply.github.com
8
+ Requires-Python: >=3.14
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Dist: sqlglot (>=30.18.0,<31.0.0)
12
+ Project-URL: Repository, https://gitlabv2.intelisis-solutions.com:8008/TICS/Interfaces/utilidades/get_objects_lib
13
+ Description-Content-Type: text/markdown
14
+
15
+ # get-objects-lib
16
+
17
+ Parser y generador de **T-SQL (SQL Server)** construido sobre [sqlglot](https://github.com/tobymao/sqlglot),
18
+ pensado para scripts reales: sin `;` entre sentencias, con `GO`, bloques `BEGIN ... END`,
19
+ procedimientos, funciones, vistas y triggers. Encima del dialecto hay un paquete (`objects`) para
20
+ analizar objetos:
21
+
22
+ - **`get_dependencies`**: qué procedimientos, funciones y tablas/vistas usa un query u objeto.
23
+ - **`standardize_object`**: convierte un `CREATE` / `ALTER` / `CREATE OR ALTER` a una forma estándar
24
+ que se puede ejecutar siempre (crea un objeto mínimo si no existe y luego aplica el `ALTER`).
25
+
26
+ ## Instalación
27
+
28
+ Requiere Python 3.14 y [Poetry](https://python-poetry.org/).
29
+
30
+ ```bash
31
+ poetry install
32
+ ```
33
+
34
+ Los ejemplos y las pruebas se corren desde la raíz del proyecto.
35
+
36
+ ## Uso
37
+
38
+ ### Dialecto: parsear y generar T-SQL
39
+
40
+ ```python
41
+ import sqlglot
42
+ from get_objects_lib import SqlServer
43
+
44
+ sql = """
45
+ IF @Modo = 1
46
+ EXEC dbo.spProcesar @ID
47
+ ELSE
48
+ BEGIN
49
+ UPDATE Venta SET Estatus = 'X' WHERE ID = @ID
50
+ PRINT 'actualizado'
51
+ END
52
+ """
53
+
54
+ for statement in sqlglot.parse(sql, read=SqlServer):
55
+ print(repr(statement)) # árbol de expresiones
56
+ print(statement.sql(dialect=SqlServer)) # de vuelta a T-SQL
57
+ ```
58
+
59
+ `sqlglot.parse` devuelve una sentencia por elemento. Un `GO` aparece como su propia sentencia
60
+ (`BatchSeparator`).
61
+
62
+ ### Dependencias de un objeto o query
63
+
64
+ ```python
65
+ from get_objects_lib import get_dependencies
66
+
67
+ for ref in get_dependencies(sql):
68
+ print(ref.kind, ref) # p.ej. "procedure dbo.spProcesar", "table_or_view Venta"
69
+ ```
70
+
71
+ Cada resultado es un `ObjectRef(kind, name, schema, catalog, server)`:
72
+
73
+ | `kind` | Qué detecta |
74
+ |---|---|
75
+ | `procedure` | `EXEC proc`, `EXEC @r = proc`, `INSERT ... EXEC proc`, `sp_executesql` |
76
+ | `function` | `dbo.fn(...)` en cualquier expresión, funciones de tabla en `FROM` y `CROSS/OUTER APPLY` |
77
+ | `table_or_view` | tablas y vistas (sin catálogo no se pueden distinguir), incluida la tabla de un trigger |
78
+
79
+ - Salen en orden de aparición y sin repetidos. Sin esquema se asume `dbo`, así que `SendHttp` y
80
+ `dbo.SendHttp` cuentan una sola vez.
81
+ - Se ignoran: tablas temporales (`#t`, `##t`), variables de tabla (`@t`), CTEs, alias,
82
+ `inserted`/`deleted` de los triggers, métodos XML (`T.c.value(...)`), funciones de tabla del
83
+ sistema (`STRING_SPLIT`, `OPENJSON`, ...) y el propio objeto que se está definiendo.
84
+ - Si el script define un objeto, se analiza todo su lote (hasta el `GO`), como hace SQL Server.
85
+ - Soporta nombres de hasta cuatro partes (`servidor.base.esquema.objeto`).
86
+
87
+ ### Forma estándar de un objeto
88
+
89
+ ```python
90
+ from get_objects_lib import standardize_object
91
+
92
+ print(standardize_object(sql)) # SET ... OFF por defecto
93
+ print(standardize_object(sql, ansi_nulls=True, quoted_identifier=True))
94
+ ```
95
+
96
+ Para un script como:
97
+
98
+ ```sql
99
+ SET ANSI_NULLS ON;
100
+ GO
101
+ -- Comentario de encabezado
102
+ CREATE PROCEDURE [dbo].[spEjemplo] @ID INT
103
+ AS
104
+ BEGIN
105
+ SELECT * FROM Venta WHERE ID = @ID
106
+ END
107
+ GO
108
+ ```
109
+
110
+ genera:
111
+
112
+ ```sql
113
+ SET ANSI_NULLS OFF;
114
+ GO
115
+ SET QUOTED_IDENTIFIER OFF;
116
+ GO
117
+ IF OBJECT_ID(N'dbo.spEjemplo', N'P') IS NULL
118
+ EXEC (N'CREATE PROCEDURE dbo.spEjemplo AS RETURN 0;');
119
+ GO
120
+ ALTER PROCEDURE [dbo].[spEjemplo] @ID INT
121
+ AS
122
+ BEGIN
123
+ SELECT * FROM Venta WHERE ID = @ID
124
+ END
125
+ GO
126
+ ```
127
+
128
+ - Acepta `CREATE`, `ALTER` y `CREATE OR ALTER` de procedimientos, funciones, vistas y triggers.
129
+ - **El cuerpo se copia tal cual** del texto original (comentarios y formato incluidos); lo que va
130
+ antes del `CREATE`/`ALTER` y los lotes posteriores (p.ej. un `GRANT` tras el `GO`) se descartan.
131
+ - El `CREATE` mínimo es del mismo tipo que el objeto real, porque `ALTER` no puede cambiarlo:
132
+
133
+ | Tipo (`OBJECT_ID`) | Objeto mínimo |
134
+ |---|---|
135
+ | `P` procedimiento | `CREATE PROCEDURE x AS RETURN 0;` |
136
+ | `V` vista | `CREATE VIEW x AS SELECT 1 AS Stub;` |
137
+ | `FN` función escalar | `CREATE FUNCTION x() RETURNS INT AS BEGIN RETURN NULL END;` |
138
+ | `IF` función de tabla en línea | `CREATE FUNCTION x() RETURNS TABLE AS RETURN SELECT 1 AS Stub;` |
139
+ | `TF` función de tabla de varias sentencias | `CREATE FUNCTION x() RETURNS @Stub TABLE (Stub INT) AS BEGIN RETURN END;` |
140
+ | `TR` trigger | `CREATE TRIGGER x ON tabla <momento> <eventos> AS RETURN;` (misma tabla, momento y eventos) |
141
+
142
+ - El nombre va sin corchetes salvo que los necesite (espacios, palabras reservadas: `dbo.[Order]`).
143
+ - Respeta el fin de línea del script (CRLF en archivos de SSMS).
144
+ - Lanza `ValueError` si el script no define un objeto, o si es un trigger DDL (`ON DATABASE`).
145
+
146
+ ## Qué cubre el dialecto
147
+
148
+ Además de lo que ya soporta el T-SQL de sqlglot, el dialecto `SqlServer` maneja:
149
+
150
+ - **Separación de sentencias sin `;`** y lotes con `GO` / `GO n` (solo en su línea; no dentro de
151
+ strings o comentarios).
152
+ - **Control de flujo**: `IF` / `ELSE IF` / `ELSE` con y sin `BEGIN`, condiciones con subconsultas
153
+ (`IF (SELECT COUNT(*) ...) > 0`), `WHILE`, `BREAK`, `CONTINUE`, `RETURN [expr]`, `GOTO` y
154
+ etiquetas, bloques `BEGIN ... END` sueltos, `BEGIN TRY ... END CATCH`, `WAITFOR`.
155
+ - **Objetos**: `CREATE` / `ALTER` / `CREATE OR ALTER` de procedimientos (con o sin paréntesis en
156
+ los parámetros, `OUTPUT`/`OUT`), funciones escalares y de tabla, vistas y triggers; opciones
157
+ `WITH RECOMPILE`, `SCHEMABINDING`, `EXECUTE AS`, `RETURNS NULL ON NULL INPUT`, etc.
158
+ - **Sentencias**: `EXEC` con parámetros con nombre, valor de retorno, SQL dinámico (`EXEC (...)`,
159
+ `EXEC (...) AT servidor`), `INSERT ... EXEC`, `INSERT ... SELECT`, `DEFAULT VALUES`,
160
+ `DELETE/UPDATE TOP (n)`, `MERGE`, CTEs, cursores (`DECLARE ... CURSOR`, `OPEN`, `FETCH`, `CLOSE`,
161
+ `DEALLOCATE`), transacciones (`BEGIN TRAN`, `COMMIT`, `ROLLBACK`, `SAVE TRAN`), `PRINT`,
162
+ `RAISERROR`, `THROW`, asignaciones compuestas (`+=`, `-=`, ...), `SET TRANSACTION ISOLATION LEVEL`.
163
+ - **Tablas**: pistas con y sin `WITH` (`FROM Venta (NOLOCK)`), índices en línea en `CREATE TABLE`
164
+ y `DECLARE @t TABLE`.
165
+ - **Comandos no modelados** (`DBCC`, `BULK INSERT`, `UPDATE STATISTICS`, `ENABLE/DISABLE TRIGGER`,
166
+ `EXECUTE AS`, `REVERT`, `CHECKPOINT`, ...) se conservan como `Command` de su sentencia.
167
+ - **Generación fiel a T-SQL**: no reemplaza funciones (`SYSDATETIME`, `SUSER_SNAME`, `DATENAME`,
168
+ `DATEDIFF`, ...), conserva el nombre de las funciones de usuario y escribe `INT`.
169
+
170
+ ### Expresiones propias
171
+
172
+ Definidas en [`get_objects_lib/dialect/expressions.py`](get_objects_lib/dialect/expressions.py):
173
+ `AlterObject`, `BatchSeparator`, `BeginEnd`, `CompoundAssignment`, `CursorStatement`, `DeclareCursor`,
174
+ `ExecParameter`, `ExecuteAt`, `FetchCursor`, `Goto`, `InlineIndex`, `Label`, `LoopControl`, `Print`,
175
+ `RaiseError`, `ReturnStatement`, `SaveTransaction`, `Throw`, `Top`, `TriggerSpec`, `TryCatch`, `WaitFor`.
176
+
177
+ ## Estructura
178
+
179
+ ```
180
+ get_objects_lib/ paquete instalable; expone SqlServer, get_dependencies,
181
+ │ standardize_object, ObjectRef, ObjectHeader, find_object_header
182
+ ├── dialect/
183
+ │ ├── dialect.py dialecto SqlServer (une tokenizer, parser y generador)
184
+ │ ├── tokenizer.py GO/PRINT/FETCH sin tragarse el script, +=, nombres de variables
185
+ │ ├── expressions.py expresiones propias de T-SQL
186
+ │ ├── generator.py generacion de T-SQL
187
+ │ ├── text_utils.py
188
+ │ └── parser/
189
+ │ ├── non_semicolon.py separa el script en sentencias (chunks) sin depender de ;
190
+ │ ├── base.py reglas de inicio de sentencia y de bloques
191
+ │ └── sql_server.py parseo de cada construccion de T-SQL
192
+ └── objects/
193
+ ├── header.py encabezado del objeto (CREATE/ALTER, tipo, nombre, lote)
194
+ ├── dependencies.py get_dependencies
195
+ └── standardize.py standardize_object
196
+ tests/ pruebas; ver tests/README.md
197
+ ├── golden.py utilidades y generacion de resultados esperados
198
+ ├── test_*.py
199
+ └── fixtures/
200
+ ├── dialect/ scripts del parser/generador + expected/
201
+ └── objects/ scripts de objetos + expected/
202
+ ```
203
+
204
+ ## Pruebas
205
+
206
+ ```bash
207
+ poetry run python -m unittest
208
+ ```
209
+
210
+ Son pruebas *golden*: cada script de `tests/fixtures/` se compara con sus resultados esperados en
211
+ `expected/` (árbol, SQL generado, dependencias y forma estándar), y además todos los scripts deben
212
+ cumplir propiedades generales (el SQL generado es estable, el árbol es el mismo con CRLF, unidos con
213
+ `GO` conservan sus sentencias). Los casos se descubren solos.
214
+
215
+ Para agregar un caso: crea el script en `tests/fixtures/dialect/` o `tests/fixtures/objects/`, genera
216
+ sus resultados esperados con `poetry run python -m tests.golden <script>.sql`, **revísalos a mano** y
217
+ agrégalo al catálogo de [`tests/README.md`](tests/README.md), que describe qué cubre cada caso.
218
+
219
+ ## Limitaciones y decisiones de diseño
220
+
221
+ - **Cuerpo de un procedimiento sin `BEGIN`**: en SQL Server llega hasta el `GO`; el parser toma una
222
+ sola sentencia (o hasta el `END` si tiene `BEGIN`). `get_dependencies` sí analiza todo el lote.
223
+ - **Un objeto por script**: si un script define varios, `standardize_object` y `get_dependencies`
224
+ toman el primero.
225
+ - **SQL dinámico**: lo que se ejecuta con `EXEC (@sql)` o `sp_executesql` no se analiza.
226
+ - **Comandos no modelados** (`Command`): su contenido no se analiza para dependencias (p.ej. la
227
+ tabla de un `BULK INSERT`).
228
+ - **Normalizaciones del generador** (equivalentes en T-SQL): agrega `AS` en los alias, escribe
229
+ `WITH (NOLOCK)` aunque el original no tenga `WITH`, `OUT` como `OUTPUT`, `IS NOT NULL` como
230
+ `NOT ... IS NULL`, y agrega alias a las columnas de tablas derivadas y CTEs (`SELECT ID AS ID`).
231
+
@@ -0,0 +1,216 @@
1
+ # get-objects-lib
2
+
3
+ Parser y generador de **T-SQL (SQL Server)** construido sobre [sqlglot](https://github.com/tobymao/sqlglot),
4
+ pensado para scripts reales: sin `;` entre sentencias, con `GO`, bloques `BEGIN ... END`,
5
+ procedimientos, funciones, vistas y triggers. Encima del dialecto hay un paquete (`objects`) para
6
+ analizar objetos:
7
+
8
+ - **`get_dependencies`**: qué procedimientos, funciones y tablas/vistas usa un query u objeto.
9
+ - **`standardize_object`**: convierte un `CREATE` / `ALTER` / `CREATE OR ALTER` a una forma estándar
10
+ que se puede ejecutar siempre (crea un objeto mínimo si no existe y luego aplica el `ALTER`).
11
+
12
+ ## Instalación
13
+
14
+ Requiere Python 3.14 y [Poetry](https://python-poetry.org/).
15
+
16
+ ```bash
17
+ poetry install
18
+ ```
19
+
20
+ Los ejemplos y las pruebas se corren desde la raíz del proyecto.
21
+
22
+ ## Uso
23
+
24
+ ### Dialecto: parsear y generar T-SQL
25
+
26
+ ```python
27
+ import sqlglot
28
+ from get_objects_lib import SqlServer
29
+
30
+ sql = """
31
+ IF @Modo = 1
32
+ EXEC dbo.spProcesar @ID
33
+ ELSE
34
+ BEGIN
35
+ UPDATE Venta SET Estatus = 'X' WHERE ID = @ID
36
+ PRINT 'actualizado'
37
+ END
38
+ """
39
+
40
+ for statement in sqlglot.parse(sql, read=SqlServer):
41
+ print(repr(statement)) # árbol de expresiones
42
+ print(statement.sql(dialect=SqlServer)) # de vuelta a T-SQL
43
+ ```
44
+
45
+ `sqlglot.parse` devuelve una sentencia por elemento. Un `GO` aparece como su propia sentencia
46
+ (`BatchSeparator`).
47
+
48
+ ### Dependencias de un objeto o query
49
+
50
+ ```python
51
+ from get_objects_lib import get_dependencies
52
+
53
+ for ref in get_dependencies(sql):
54
+ print(ref.kind, ref) # p.ej. "procedure dbo.spProcesar", "table_or_view Venta"
55
+ ```
56
+
57
+ Cada resultado es un `ObjectRef(kind, name, schema, catalog, server)`:
58
+
59
+ | `kind` | Qué detecta |
60
+ |---|---|
61
+ | `procedure` | `EXEC proc`, `EXEC @r = proc`, `INSERT ... EXEC proc`, `sp_executesql` |
62
+ | `function` | `dbo.fn(...)` en cualquier expresión, funciones de tabla en `FROM` y `CROSS/OUTER APPLY` |
63
+ | `table_or_view` | tablas y vistas (sin catálogo no se pueden distinguir), incluida la tabla de un trigger |
64
+
65
+ - Salen en orden de aparición y sin repetidos. Sin esquema se asume `dbo`, así que `SendHttp` y
66
+ `dbo.SendHttp` cuentan una sola vez.
67
+ - Se ignoran: tablas temporales (`#t`, `##t`), variables de tabla (`@t`), CTEs, alias,
68
+ `inserted`/`deleted` de los triggers, métodos XML (`T.c.value(...)`), funciones de tabla del
69
+ sistema (`STRING_SPLIT`, `OPENJSON`, ...) y el propio objeto que se está definiendo.
70
+ - Si el script define un objeto, se analiza todo su lote (hasta el `GO`), como hace SQL Server.
71
+ - Soporta nombres de hasta cuatro partes (`servidor.base.esquema.objeto`).
72
+
73
+ ### Forma estándar de un objeto
74
+
75
+ ```python
76
+ from get_objects_lib import standardize_object
77
+
78
+ print(standardize_object(sql)) # SET ... OFF por defecto
79
+ print(standardize_object(sql, ansi_nulls=True, quoted_identifier=True))
80
+ ```
81
+
82
+ Para un script como:
83
+
84
+ ```sql
85
+ SET ANSI_NULLS ON;
86
+ GO
87
+ -- Comentario de encabezado
88
+ CREATE PROCEDURE [dbo].[spEjemplo] @ID INT
89
+ AS
90
+ BEGIN
91
+ SELECT * FROM Venta WHERE ID = @ID
92
+ END
93
+ GO
94
+ ```
95
+
96
+ genera:
97
+
98
+ ```sql
99
+ SET ANSI_NULLS OFF;
100
+ GO
101
+ SET QUOTED_IDENTIFIER OFF;
102
+ GO
103
+ IF OBJECT_ID(N'dbo.spEjemplo', N'P') IS NULL
104
+ EXEC (N'CREATE PROCEDURE dbo.spEjemplo AS RETURN 0;');
105
+ GO
106
+ ALTER PROCEDURE [dbo].[spEjemplo] @ID INT
107
+ AS
108
+ BEGIN
109
+ SELECT * FROM Venta WHERE ID = @ID
110
+ END
111
+ GO
112
+ ```
113
+
114
+ - Acepta `CREATE`, `ALTER` y `CREATE OR ALTER` de procedimientos, funciones, vistas y triggers.
115
+ - **El cuerpo se copia tal cual** del texto original (comentarios y formato incluidos); lo que va
116
+ antes del `CREATE`/`ALTER` y los lotes posteriores (p.ej. un `GRANT` tras el `GO`) se descartan.
117
+ - El `CREATE` mínimo es del mismo tipo que el objeto real, porque `ALTER` no puede cambiarlo:
118
+
119
+ | Tipo (`OBJECT_ID`) | Objeto mínimo |
120
+ |---|---|
121
+ | `P` procedimiento | `CREATE PROCEDURE x AS RETURN 0;` |
122
+ | `V` vista | `CREATE VIEW x AS SELECT 1 AS Stub;` |
123
+ | `FN` función escalar | `CREATE FUNCTION x() RETURNS INT AS BEGIN RETURN NULL END;` |
124
+ | `IF` función de tabla en línea | `CREATE FUNCTION x() RETURNS TABLE AS RETURN SELECT 1 AS Stub;` |
125
+ | `TF` función de tabla de varias sentencias | `CREATE FUNCTION x() RETURNS @Stub TABLE (Stub INT) AS BEGIN RETURN END;` |
126
+ | `TR` trigger | `CREATE TRIGGER x ON tabla <momento> <eventos> AS RETURN;` (misma tabla, momento y eventos) |
127
+
128
+ - El nombre va sin corchetes salvo que los necesite (espacios, palabras reservadas: `dbo.[Order]`).
129
+ - Respeta el fin de línea del script (CRLF en archivos de SSMS).
130
+ - Lanza `ValueError` si el script no define un objeto, o si es un trigger DDL (`ON DATABASE`).
131
+
132
+ ## Qué cubre el dialecto
133
+
134
+ Además de lo que ya soporta el T-SQL de sqlglot, el dialecto `SqlServer` maneja:
135
+
136
+ - **Separación de sentencias sin `;`** y lotes con `GO` / `GO n` (solo en su línea; no dentro de
137
+ strings o comentarios).
138
+ - **Control de flujo**: `IF` / `ELSE IF` / `ELSE` con y sin `BEGIN`, condiciones con subconsultas
139
+ (`IF (SELECT COUNT(*) ...) > 0`), `WHILE`, `BREAK`, `CONTINUE`, `RETURN [expr]`, `GOTO` y
140
+ etiquetas, bloques `BEGIN ... END` sueltos, `BEGIN TRY ... END CATCH`, `WAITFOR`.
141
+ - **Objetos**: `CREATE` / `ALTER` / `CREATE OR ALTER` de procedimientos (con o sin paréntesis en
142
+ los parámetros, `OUTPUT`/`OUT`), funciones escalares y de tabla, vistas y triggers; opciones
143
+ `WITH RECOMPILE`, `SCHEMABINDING`, `EXECUTE AS`, `RETURNS NULL ON NULL INPUT`, etc.
144
+ - **Sentencias**: `EXEC` con parámetros con nombre, valor de retorno, SQL dinámico (`EXEC (...)`,
145
+ `EXEC (...) AT servidor`), `INSERT ... EXEC`, `INSERT ... SELECT`, `DEFAULT VALUES`,
146
+ `DELETE/UPDATE TOP (n)`, `MERGE`, CTEs, cursores (`DECLARE ... CURSOR`, `OPEN`, `FETCH`, `CLOSE`,
147
+ `DEALLOCATE`), transacciones (`BEGIN TRAN`, `COMMIT`, `ROLLBACK`, `SAVE TRAN`), `PRINT`,
148
+ `RAISERROR`, `THROW`, asignaciones compuestas (`+=`, `-=`, ...), `SET TRANSACTION ISOLATION LEVEL`.
149
+ - **Tablas**: pistas con y sin `WITH` (`FROM Venta (NOLOCK)`), índices en línea en `CREATE TABLE`
150
+ y `DECLARE @t TABLE`.
151
+ - **Comandos no modelados** (`DBCC`, `BULK INSERT`, `UPDATE STATISTICS`, `ENABLE/DISABLE TRIGGER`,
152
+ `EXECUTE AS`, `REVERT`, `CHECKPOINT`, ...) se conservan como `Command` de su sentencia.
153
+ - **Generación fiel a T-SQL**: no reemplaza funciones (`SYSDATETIME`, `SUSER_SNAME`, `DATENAME`,
154
+ `DATEDIFF`, ...), conserva el nombre de las funciones de usuario y escribe `INT`.
155
+
156
+ ### Expresiones propias
157
+
158
+ Definidas en [`get_objects_lib/dialect/expressions.py`](get_objects_lib/dialect/expressions.py):
159
+ `AlterObject`, `BatchSeparator`, `BeginEnd`, `CompoundAssignment`, `CursorStatement`, `DeclareCursor`,
160
+ `ExecParameter`, `ExecuteAt`, `FetchCursor`, `Goto`, `InlineIndex`, `Label`, `LoopControl`, `Print`,
161
+ `RaiseError`, `ReturnStatement`, `SaveTransaction`, `Throw`, `Top`, `TriggerSpec`, `TryCatch`, `WaitFor`.
162
+
163
+ ## Estructura
164
+
165
+ ```
166
+ get_objects_lib/ paquete instalable; expone SqlServer, get_dependencies,
167
+ │ standardize_object, ObjectRef, ObjectHeader, find_object_header
168
+ ├── dialect/
169
+ │ ├── dialect.py dialecto SqlServer (une tokenizer, parser y generador)
170
+ │ ├── tokenizer.py GO/PRINT/FETCH sin tragarse el script, +=, nombres de variables
171
+ │ ├── expressions.py expresiones propias de T-SQL
172
+ │ ├── generator.py generacion de T-SQL
173
+ │ ├── text_utils.py
174
+ │ └── parser/
175
+ │ ├── non_semicolon.py separa el script en sentencias (chunks) sin depender de ;
176
+ │ ├── base.py reglas de inicio de sentencia y de bloques
177
+ │ └── sql_server.py parseo de cada construccion de T-SQL
178
+ └── objects/
179
+ ├── header.py encabezado del objeto (CREATE/ALTER, tipo, nombre, lote)
180
+ ├── dependencies.py get_dependencies
181
+ └── standardize.py standardize_object
182
+ tests/ pruebas; ver tests/README.md
183
+ ├── golden.py utilidades y generacion de resultados esperados
184
+ ├── test_*.py
185
+ └── fixtures/
186
+ ├── dialect/ scripts del parser/generador + expected/
187
+ └── objects/ scripts de objetos + expected/
188
+ ```
189
+
190
+ ## Pruebas
191
+
192
+ ```bash
193
+ poetry run python -m unittest
194
+ ```
195
+
196
+ Son pruebas *golden*: cada script de `tests/fixtures/` se compara con sus resultados esperados en
197
+ `expected/` (árbol, SQL generado, dependencias y forma estándar), y además todos los scripts deben
198
+ cumplir propiedades generales (el SQL generado es estable, el árbol es el mismo con CRLF, unidos con
199
+ `GO` conservan sus sentencias). Los casos se descubren solos.
200
+
201
+ Para agregar un caso: crea el script en `tests/fixtures/dialect/` o `tests/fixtures/objects/`, genera
202
+ sus resultados esperados con `poetry run python -m tests.golden <script>.sql`, **revísalos a mano** y
203
+ agrégalo al catálogo de [`tests/README.md`](tests/README.md), que describe qué cubre cada caso.
204
+
205
+ ## Limitaciones y decisiones de diseño
206
+
207
+ - **Cuerpo de un procedimiento sin `BEGIN`**: en SQL Server llega hasta el `GO`; el parser toma una
208
+ sola sentencia (o hasta el `END` si tiene `BEGIN`). `get_dependencies` sí analiza todo el lote.
209
+ - **Un objeto por script**: si un script define varios, `standardize_object` y `get_dependencies`
210
+ toman el primero.
211
+ - **SQL dinámico**: lo que se ejecuta con `EXEC (@sql)` o `sp_executesql` no se analiza.
212
+ - **Comandos no modelados** (`Command`): su contenido no se analiza para dependencias (p.ej. la
213
+ tabla de un `BULK INSERT`).
214
+ - **Normalizaciones del generador** (equivalentes en T-SQL): agrega `AS` en los alias, escribe
215
+ `WITH (NOLOCK)` aunque el original no tenga `WITH`, `OUT` como `OUTPUT`, `IS NOT NULL` como
216
+ `NOT ... IS NULL`, y agrega alias a las columnas de tablas derivadas y CTEs (`SELECT ID AS ID`).
@@ -0,0 +1,12 @@
1
+ """Parser y generador de T-SQL sobre sqlglot, y analisis de objetos de SQL Server."""
2
+ from get_objects_lib.dialect import SqlServer
3
+ from get_objects_lib.objects import ObjectHeader, ObjectRef, find_object_header, get_dependencies, standardize_object
4
+
5
+ __all__ = [
6
+ 'SqlServer',
7
+ 'ObjectHeader',
8
+ 'ObjectRef',
9
+ 'find_object_header',
10
+ 'get_dependencies',
11
+ 'standardize_object',
12
+ ]
@@ -0,0 +1,3 @@
1
+ from get_objects_lib.dialect.dialect import SqlServer
2
+
3
+ __all__ = ['SqlServer']
@@ -0,0 +1,11 @@
1
+ from sqlglot.dialects.tsql import TSQL
2
+
3
+ from get_objects_lib.dialect.parser.sql_server import SqlServerParser
4
+ from get_objects_lib.dialect.tokenizer import SqlServerTokenizer
5
+ from get_objects_lib.dialect.generator import SqlServerGenerator
6
+
7
+
8
+ class SqlServer(TSQL):
9
+ Tokenizer = SqlServerTokenizer
10
+ Parser = SqlServerParser
11
+ Generator = SqlServerGenerator
@@ -0,0 +1,116 @@
1
+ from sqlglot import exp
2
+
3
+
4
+ class ExecParameter(exp.Parameter):
5
+ arg_types = {"this": True, "expression": False, "output": False}
6
+
7
+
8
+ class ReturnStatement(exp.Return):
9
+ """RETURN como sentencia; el valor es opcional (RETURN a secas)."""
10
+ arg_types = {"this": False}
11
+
12
+
13
+ class BatchSeparator(exp.Expression):
14
+ """GO [n]: separa lotes; this es el numero de repeticiones."""
15
+ arg_types = {"this": False}
16
+
17
+
18
+ class LoopControl(exp.Expression):
19
+ """BREAK / CONTINUE dentro de un WHILE; this es la palabra clave."""
20
+ arg_types = {"this": True}
21
+
22
+
23
+ class Print(exp.Expression):
24
+ arg_types = {"this": True}
25
+
26
+
27
+ class RaiseError(exp.Expression):
28
+ """RAISERROR (mensaje, severidad, estado [, args]) [WITH opciones]"""
29
+ arg_types = {"expressions": True, "options": False}
30
+
31
+
32
+ class Throw(exp.Expression):
33
+ """THROW [numero, mensaje, estado]; sin argumentos relanza el error."""
34
+ arg_types = {"expressions": False}
35
+
36
+
37
+ class SaveTransaction(exp.Expression):
38
+ """SAVE TRAN[SACTION] nombre"""
39
+ arg_types = {"this": True}
40
+
41
+
42
+ class BeginEnd(exp.Expression):
43
+ """Bloque BEGIN ... END suelto (sin IF/WHILE/procedimiento que lo contenga)."""
44
+ arg_types = {"this": True}
45
+
46
+
47
+ class TryCatch(exp.Expression):
48
+ """BEGIN TRY ... END TRY BEGIN CATCH ... END CATCH"""
49
+ arg_types = {"this": True, "catch": True}
50
+
51
+
52
+ class DeclareCursor(exp.Expression):
53
+ """DECLARE nombre CURSOR [opciones] FOR select [FOR UPDATE [OF columnas] | FOR READ ONLY]"""
54
+ arg_types = {"this": True, "options": False, "expression": True, "for_update": False, "columns": False, "read_only": False}
55
+
56
+
57
+ class CursorStatement(exp.Expression):
58
+ """OPEN / CLOSE / DEALLOCATE [GLOBAL] cursor"""
59
+ arg_types = {"kind": True, "this": True, "global_": False}
60
+
61
+
62
+ class FetchCursor(exp.Expression):
63
+ """FETCH [NEXT | PRIOR | FIRST | LAST | ABSOLUTE n | RELATIVE n] [FROM] cursor [INTO @variables]"""
64
+ arg_types = {"this": True, "direction": False, "count": False, "global_": False, "into": False}
65
+
66
+
67
+ class Top(exp.Expression):
68
+ """TOP (n) [PERCENT] de un DELETE / UPDATE; se guarda en su arg "limit"."""
69
+ arg_types = {"this": True, "percent": False}
70
+
71
+
72
+ class Label(exp.Expression):
73
+ """Etiqueta de GOTO: "Nombre:"."""
74
+ arg_types = {"this": True}
75
+
76
+
77
+ class Goto(exp.Expression):
78
+ arg_types = {"this": True}
79
+
80
+
81
+ class WaitFor(exp.Expression):
82
+ """WAITFOR DELAY | TIME expresion"""
83
+ arg_types = {"kind": True, "this": True}
84
+
85
+
86
+ class CompoundAssignment(exp.Expression):
87
+ """@a += 1, col -= 2, ...; op es el operador sin el "=" (+, -, *, /, %, &, |, ^)."""
88
+ arg_types = {"this": True, "expression": True, "op": True}
89
+
90
+
91
+ class TriggerSpec(exp.Expression):
92
+ """
93
+ Encabezado de un trigger de T-SQL: ON tabla [WITH opciones]
94
+ {FOR | AFTER | INSTEAD OF} eventos [NOT FOR REPLICATION]
95
+ """
96
+ arg_types = {"table": True, "options": False, "timing": True, "events": True, "not_for_replication": False}
97
+
98
+
99
+ class ExecuteAt(exp.Execute):
100
+ """EXEC (sql [, parametros]) AT servidor_vinculado"""
101
+ arg_types = {**exp.Execute.arg_types, "at": True}
102
+
103
+
104
+ class InlineIndex(exp.Expression):
105
+ """
106
+ Indice en la definicion de una tabla: INDEX nombre [UNIQUE]
107
+ [CLUSTERED | NONCLUSTERED] (columnas) [INCLUDE (...)] [WHERE ...] [WITH (...)]
108
+ """
109
+ arg_types = {
110
+ "this": True, "unique": False, "clustered": False, "expressions": True,
111
+ "include": False, "where": False, "options": False,
112
+ }
113
+
114
+
115
+ class AlterObject(exp.Create):
116
+ """ALTER PROCEDURE/FUNCTION/VIEW/TRIGGER: misma estructura que su CREATE."""