{"id":25806003,"url":"https://github.com/alipsa/data-utils","last_synced_at":"2026-02-07T17:06:53.327Z","repository":{"id":49380514,"uuid":"505573732","full_name":"Alipsa/data-utils","owner":"Alipsa","description":"Groovy data utils","archived":false,"fork":false,"pushed_at":"2025-02-25T17:47:39.000Z","size":1251,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-25T18:39:40.565Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Groovy","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Alipsa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-06-20T19:33:23.000Z","updated_at":"2025-02-25T17:47:42.000Z","dependencies_parsed_at":"2023-01-31T07:15:46.730Z","dependency_job_id":"be6b53f7-1eab-4a73-b923-da8bcefa523a","html_url":"https://github.com/Alipsa/data-utils","commit_stats":null,"previous_names":["pernyfelt/data-utils"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alipsa%2Fdata-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alipsa%2Fdata-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alipsa%2Fdata-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Alipsa%2Fdata-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Alipsa","download_url":"https://codeload.github.com/Alipsa/data-utils/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241052529,"owners_count":19901043,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-27T19:52:36.268Z","updated_at":"2026-02-07T17:06:53.321Z","avatar_url":"https://github.com/Alipsa.png","language":"Groovy","funding_links":[],"categories":[],"sub_categories":[],"readme":"# data-utils\n\nGroovy SQL utilities library that solves the JDBC driver classloader issue when using `@Grab` in IDEs like [Gade](https://github.com/Alipsa/gade) and [Ride](https://github.com/Alipsa/ride).\n\nThe standard approach with `@Grab` relies on the System classloader, requiring you to copy JDBC driver JARs to the lib folder and restart the IDE. This library uses reflection to load drivers from the appropriate classloader, allowing seamless database connections without IDE restarts.\n\n## Features\n\n- **SqlUtil**: Drop-in replacement for `groovy.sql.Sql` factory methods with proper classloader handling\n- **ConnectionInfo**: Data class for managing database connection configuration\n- **SqlTypeMapper**: Database-specific SQL type mapping for Java types\n- **DataBaseProvider**: Enum with metadata for 16 supported database systems\n\n## Installation\n\ndata-utils is available from Maven Central.\n\n**Gradle:**\n```groovy\nimplementation \"se.alipsa.groovy:data-utils:2.0.5\"\n```\n\n**Maven:**\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ese.alipsa.groovy\u003c/groupId\u003e\n    \u003cartifactId\u003edata-utils\u003c/artifactId\u003e\n    \u003cversion\u003e2.0.5\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Quick Start\n\n```groovy\n@Grab('se.alipsa.groovy:data-utils:2.0.5')\n@Grab('org.postgresql:postgresql:42.7.8')\n\nimport se.alipsa.groovy.datautil.SqlUtil\n\nSqlUtil.withInstance(\"jdbc:postgresql://localhost:5432/mydb\", \"user\", \"password\") { sql -\u003e\n    sql.eachRow('SELECT * FROM users') { row -\u003e\n        println \"${row.id}: ${row.name}\"\n    }\n}\n```\n\n## Database Examples\n\n### H2 (In-Memory)\n\n```groovy\n@Grab('se.alipsa.groovy:data-utils:2.0.5')\n@Grab('com.h2database:h2:2.2.224')\n\nimport se.alipsa.groovy.datautil.SqlUtil\n\n// Driver class is auto-detected from URL\nSqlUtil.withInstance(\"jdbc:h2:mem:testdb\", \"sa\", \"\") { sql -\u003e\n    sql.execute '''\n        CREATE TABLE users (\n            id INT PRIMARY KEY,\n            name VARCHAR(100)\n        )\n    '''\n    sql.execute \"INSERT INTO users VALUES (1, 'Alice')\"\n\n    sql.eachRow('SELECT * FROM users') { row -\u003e\n        println \"${row.id}: ${row.name}\"\n    }\n}\n```\n\n### H2 (File-based)\n\n```groovy\n@Grab('se.alipsa.groovy:data-utils:2.0.5')\n@Grab('com.h2database:h2:2.2.224')\n\nimport se.alipsa.groovy.datautil.SqlUtil\n\ndef dbPath = new File(System.getProperty('java.io.tmpdir'), 'mydb').absolutePath\nSqlUtil.withInstance(\"jdbc:h2:file:${dbPath}\", \"sa\", \"password\") { sql -\u003e\n    // Use the connection\n}\n```\n\n### MySQL / MariaDB\n\n```groovy\n@Grab('se.alipsa.groovy:data-utils:2.0.5')\n@Grab('com.mysql:mysql-connector-j:8.2.0')\n\nimport se.alipsa.groovy.datautil.SqlUtil\n\nSqlUtil.withInstance(\"jdbc:mysql://localhost:3306/mydb\", \"root\", \"password\") { sql -\u003e\n    sql.eachRow('SELECT * FROM products') { row -\u003e\n        println row.name\n    }\n}\n```\n\n```groovy\n@Grab('se.alipsa.groovy:data-utils:2.0.5')\n@Grab('org.mariadb.jdbc:mariadb-java-client:3.3.0')\n\nimport se.alipsa.groovy.datautil.SqlUtil\n\nSqlUtil.withInstance(\"jdbc:mariadb://localhost:3306/mydb\", \"root\", \"password\") { sql -\u003e\n    // Use the connection\n}\n```\n\n### PostgreSQL\n\n```groovy\n@Grab('se.alipsa.groovy:data-utils:2.0.5')\n@Grab('org.postgresql:postgresql:42.7.8')\n\nimport se.alipsa.groovy.datautil.SqlUtil\n\nSqlUtil.withInstance(\"jdbc:postgresql://localhost:5432/mydb\", \"postgres\", \"password\") { sql -\u003e\n    sql.eachRow('SELECT * FROM orders WHERE status = ?', ['pending']) { row -\u003e\n        println \"Order ${row.id}: ${row.total}\"\n    }\n}\n```\n\n### SQL Server\n\n```groovy\n@Grab('se.alipsa.groovy:data-utils:2.0.5')\n@Grab('com.microsoft.sqlserver:mssql-jdbc:12.4.2.jre11')\n\nimport se.alipsa.groovy.datautil.SqlUtil\n\nSqlUtil.withInstance(\n    \"jdbc:sqlserver://localhost:1433;databaseName=mydb;encrypt=false\",\n    \"sa\", \"password\"\n) { sql -\u003e\n    sql.eachRow('SELECT TOP 10 * FROM customers') { row -\u003e\n        println row.name\n    }\n}\n```\n\n### Oracle\n\n```groovy\n@Grab('se.alipsa.groovy:data-utils:2.0.5')\n@Grab('com.oracle.database.jdbc:ojdbc11:23.3.0.23.09')\n\nimport se.alipsa.groovy.datautil.SqlUtil\n\nSqlUtil.withInstance(\n    \"jdbc:oracle:thin:@localhost:1521:ORCL\",\n    \"system\", \"password\"\n) { sql -\u003e\n    sql.eachRow('SELECT * FROM employees WHERE ROWNUM \u003c= 10') { row -\u003e\n        println row.employee_name\n    }\n}\n```\n\n### SQLite\n\n```groovy\n@Grab('se.alipsa.groovy:data-utils:2.0.5')\n@Grab('org.xerial:sqlite-jdbc:3.44.1.0')\n\nimport se.alipsa.groovy.datautil.SqlUtil\n\nSqlUtil.withInstance(\"jdbc:sqlite:mydb.sqlite\", null, null) { sql -\u003e\n    sql.execute 'CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)'\n    sql.execute \"INSERT INTO items (name) VALUES ('Test')\"\n}\n```\n\n### Apache Derby\n\n```groovy\n@Grab('se.alipsa.groovy:data-utils:2.0.5')\n@Grab('org.apache.derby:derby:10.16.1.1')\n@Grab('org.apache.derby:derbytools:10.16.1.1')\n\nimport se.alipsa.groovy.datautil.SqlUtil\n\nSqlUtil.withInstance(\"jdbc:derby:memory:mydb;create=true\", null, null) { sql -\u003e\n    sql.execute '''\n        CREATE TABLE test (\n            id INT PRIMARY KEY,\n            data VARCHAR(100)\n        )\n    '''\n}\n```\n\n## API Reference\n\n### SqlUtil\n\nThe main utility class providing factory methods for database connections.\n\n#### Factory Methods\n\n**withInstance** - Execute code with auto-closing connection:\n```groovy\n// With explicit driver\nSqlUtil.withInstance(url, user, password, driverClassName) { sql -\u003e ... }\n\n// With auto-detected driver\nSqlUtil.withInstance(url, user, password) { sql -\u003e ... }\n\n// URL-only (credentials in URL)\nSqlUtil.withInstance(url) { sql -\u003e ... }\n\n// With ConnectionInfo\nSqlUtil.withInstance(connectionInfo) { sql -\u003e ... }\n```\n\n**newInstance** - Create a Sql instance (caller manages closing):\n```groovy\ndef sql = SqlUtil.newInstance(url, user, password)\ntry {\n    // Use sql\n} finally {\n    sql.close()\n}\n```\n\n**connect** - Get raw JDBC Connection:\n```groovy\ndef connection = SqlUtil.connect(driverClass, jdbcUrl, properties)\n```\n\n**driver** - Load a JDBC driver instance:\n```groovy\ndef driver = SqlUtil.driver(driverClassName, callerClass)\n```\n\n**getDriverClassName** - Auto-detect driver class from URL:\n```groovy\ndef driverClass = SqlUtil.getDriverClassName(\"jdbc:postgresql://localhost/db\")\n// Returns: \"org.postgresql.Driver\"\n```\n\n### ConnectionInfo\n\nA data class for managing connection configuration.\n\n```groovy\nimport se.alipsa.groovy.datautil.ConnectionInfo\n\n// Full constructor\ndef ci = new ConnectionInfo(\n    \"myConnection\",                    // name\n    \"org.postgresql:postgresql:42.7.8\", // dependency (for @Grab)\n    \"org.postgresql.Driver\",           // driver class\n    \"jdbc:postgresql://localhost/db\",  // url\n    \"user\",                            // user\n    \"password\"                         // password\n)\n\n// Using setters (driver auto-detected from URL)\ndef ci = new ConnectionInfo()\nci.name = \"myConnection\"\nci.url = \"jdbc:postgresql://localhost/db\"  // Sets driver automatically\nci.user = \"user\"\nci.password = \"password\"\n\n// Fluent password setting\nci.withPassword(\"newPassword\")\n\n// Get as Properties (for JDBC)\nProperties props = ci.properties\n\n// Serialize to JSON (password masked)\nString json = ci.asJson()\n\n// Get dependency version\nString version = ci.dependencyVersion  // e.g., \"42.7.8\"\n```\n\n### SqlTypeMapper\n\nMaps Java types to database-specific SQL types. Useful for DDL generation.\n\n```groovy\nimport se.alipsa.groovy.datautil.sqltypes.SqlTypeMapper\nimport se.alipsa.groovy.datautil.DataBaseProvider\n\n// Create mapper for specific database\ndef mapper = SqlTypeMapper.create(DataBaseProvider.POSTGRESQL)\n\n// Or from JDBC URL\ndef mapper = SqlTypeMapper.create(\"jdbc:postgresql://localhost/db\")\n\n// Or from ConnectionInfo\ndef mapper = SqlTypeMapper.create(connectionInfo)\n\n// Get SQL type for Java class\nmapper.sqlType(String)        // \"VARCHAR\"\nmapper.sqlType(Integer)       // \"INTEGER\"\nmapper.sqlType(BigDecimal)    // \"NUMERIC(38, 10)\"\nmapper.sqlType(LocalDateTime) // \"TIMESTAMP\"\n\n// With size hints\nmapper.typeForString(255)     // \"VARCHAR(255)\"\nmapper.typeForBigDecimal(10, 2) // \"NUMERIC(10, 2)\"\n\n// Get JDBC type constant\nmapper.jdbcType(String)       // java.sql.Types.VARCHAR\nmapper.jdbcType(LocalDate)    // java.sql.Types.DATE\n```\n\n#### Database-Specific Mappings\n\n| Java Type        | Default   | PostgreSQL | SQL Server   | Derby                |\n|------------------|-----------|------------|--------------|----------------------|\n| `Byte`           | TINYINT   | SMALLINT   | TINYINT      | SMALLINT             |\n| `Double`         | DOUBLE    | DOUBLE     | FLOAT        | DOUBLE               |\n| `Timestamp`      | TIMESTAMP | TIMESTAMP  | datetime2(3) | TIMESTAMP            |\n| `LocalDateTime`  | TIMESTAMP | TIMESTAMP  | datetime2    | TIMESTAMP            |\n| `String (\u003e8000)` | CLOB      | TEXT       | VARCHAR(max) | CLOB                 |\n| `byte[]`         | VARBINARY | VARBINARY  | VARBINARY    | VARCHAR FOR BIT DATA |\n\n### DataBaseProvider\n\nEnum containing metadata for supported databases.\n\n```groovy\nimport se.alipsa.groovy.datautil.DataBaseProvider\n\n// Get provider from URL\ndef provider = DataBaseProvider.fromUrl(\"jdbc:postgresql://localhost/db\")\n// Returns: DataBaseProvider.POSTGRESQL\n\n// Access metadata\nprovider.urlStart      // \"jdbc:postgresql:\"\nprovider.dependency    // \"org.postgresql:postgresql:42.7.1\"\n\n// Supported providers\nDataBaseProvider.values().each { println it.name() }\n// H2, POSTGRESQL, MYSQL, MARIADB, MSSQL, ORACLE, DERBY, HSQLDB, SQLLITE, DB2, etc.\n```\n\n## Thread Safety and Connection Pooling\n\n### Thread Safety\n\n- **SqlUtil factory methods** are thread-safe and can be called from multiple threads\n- **Sql instances** returned by `newInstance()` are NOT thread-safe - each thread should have its own instance\n- **ConnectionInfo** is a simple data class with no synchronization - treat as effectively immutable after configuration\n\n### Connection Pooling Recommendations\n\nThis library creates direct JDBC connections without pooling. For production applications with concurrent access, use a connection pool:\n\n**HikariCP (Recommended):**\n```groovy\n@Grab('com.zaxxer:HikariCP:5.1.0')\n@Grab('org.postgresql:postgresql:42.7.8')\n\nimport com.zaxxer.hikari.HikariConfig\nimport com.zaxxer.hikari.HikariDataSource\nimport groovy.sql.Sql\n\ndef config = new HikariConfig()\nconfig.jdbcUrl = \"jdbc:postgresql://localhost/mydb\"\nconfig.username = \"user\"\nconfig.password = \"password\"\nconfig.maximumPoolSize = 10\n\ndef dataSource = new HikariDataSource(config)\n\n// Use pooled connections\ndef sql = new Sql(dataSource)\ntry {\n    sql.eachRow('SELECT * FROM users') { row -\u003e\n        println row.name\n    }\n} finally {\n    sql.close()  // Returns connection to pool\n}\n\n// Shutdown pool when done\ndataSource.close()\n```\n\n**When to use connection pooling:**\n- Web applications with concurrent requests\n- Multi-threaded batch processing\n- Long-running applications\n- When connection establishment time is a bottleneck\n\n**When direct connections (this library) are sufficient:**\n- Scripts and one-off tasks\n- Single-threaded applications\n- Development and testing\n- Low-concurrency scenarios\n\n### Best Practices\n\n1. **Always close connections** - Use `withInstance` for automatic closing, or try-finally with `newInstance`\n2. **One Sql instance per thread** - Don't share Sql objects between threads\n3. **Use parameterized queries** - Prevent SQL injection: `sql.eachRow('SELECT * FROM users WHERE id = ?', [userId])`\n4. **Handle transactions explicitly** when needed:\n   ```groovy\n   sql.withTransaction {\n       sql.execute \"UPDATE accounts SET balance = balance - 100 WHERE id = 1\"\n       sql.execute \"UPDATE accounts SET balance = balance + 100 WHERE id = 2\"\n   }\n   ```\n\n## Supported Databases\n\n| Database   | URL Prefix         | Auto-detected Driver                           |\n|------------|--------------------|------------------------------------------------|\n| H2         | `jdbc:h2:`         | `org.h2.Driver`                                |\n| PostgreSQL | `jdbc:postgresql:` | `org.postgresql.Driver`                        |\n| MySQL      | `jdbc:mysql:`      | `com.mysql.jdbc.Driver`                        |\n| MariaDB    | `jdbc:mariadb:`    | `org.mariadb.jdbc.Driver`                      |\n| SQL Server | `jdbc:sqlserver:`  | `com.microsoft.sqlserver.jdbc.SQLServerDriver` |\n| Oracle     | `jdbc:oracle:`     | `oracle.jdbc.OracleDriver`                     |\n| Derby      | `jdbc:derby:`      | `org.apache.derby.jdbc.EmbeddedDriver`         |\n| HSQLDB     | `jdbc:hsqldb:`     | `org.hsqldb.jdbc.JDBCDriver`                   |\n| SQLite     | `jdbc:sqlite:`     | `org.sqlite.JDBC`                              |\n| DB2        | `jdbc:db2:`        | `com.ibm.db2.jcc.DB2Driver`                    |\n\n## More Examples\n\nSee the [test classes](https://github.com/Alipsa/data-utils/tree/master/src/test/groovy/test/alipsa/groovy/datautil) for more examples:\n- [SqlUtilTest](https://github.com/Alipsa/data-utils/blob/master/src/test/groovy/test/alipsa/groovy/datautil/SqlUtilTest.groovy)\n- [SqlTypeMapperTest](https://github.com/Alipsa/data-utils/blob/master/src/test/groovy/test/alipsa/groovy/datautil/SqlTypeMapperTest.groovy)\n- [ConnectionInfoTest](https://github.com/Alipsa/data-utils/blob/master/src/test/groovy/test/alipsa/groovy/datautil/ConnectionInfoTest.groovy)\n\n## Version History\n\nSee [releases.md](releases.md) for the full version history.\n\n## License\n\nMIT License - see [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falipsa%2Fdata-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falipsa%2Fdata-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falipsa%2Fdata-utils/lists"}