{"id":20904701,"url":"https://github.com/sage/mysql_framework","last_synced_at":"2025-05-13T05:30:47.496Z","repository":{"id":53802711,"uuid":"138882004","full_name":"Sage/mysql_framework","owner":"Sage","description":null,"archived":false,"fork":false,"pushed_at":"2024-09-12T12:18:23.000Z","size":165,"stargazers_count":1,"open_issues_count":3,"forks_count":1,"subscribers_count":29,"default_branch":"master","last_synced_at":"2025-04-29T05:04:55.811Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Sage.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2018-06-27T12:59:17.000Z","updated_at":"2024-09-12T12:18:25.000Z","dependencies_parsed_at":"2024-11-18T13:20:56.867Z","dependency_job_id":"6efed5ab-4adc-4717-ad42-6e572010da2f","html_url":"https://github.com/Sage/mysql_framework","commit_stats":{"total_commits":78,"total_committers":10,"mean_commits":7.8,"dds":0.6538461538461539,"last_synced_commit":"3eb56ab03017238d53f4d015246d7aa38fe5e8c1"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sage%2Fmysql_framework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sage%2Fmysql_framework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sage%2Fmysql_framework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sage%2Fmysql_framework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Sage","download_url":"https://codeload.github.com/Sage/mysql_framework/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253882708,"owners_count":21978539,"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":"2024-11-18T13:18:33.441Z","updated_at":"2025-05-13T05:30:47.475Z","avatar_url":"https://github.com/Sage.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mysql_Framework\n\n[![RSpec](https://github.com/Sage/mysql_framework/actions/workflows/rspec.yml/badge.svg)](https://github.com/Sage/mysql_framework/actions/workflows/rspec.yml)\n[![Maintainability](https://api.codeclimate.com/v1/badges/36068a1f03ea88d08b86/maintainability)](https://codeclimate.com/github/Sage/mysql_framework/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/36068a1f03ea88d08b86/test_coverage)](https://codeclimate.com/github/Sage/mysql_framework/test_coverage)\n[![Gem Version](https://badge.fury.io/rb/mysql_framework.svg)](https://badge.fury.io/rb/mysql_framework)\n\nWelcome to Mysql_Framework, this is a lightweight framework that provides managers to help with interacting with mysql.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'mysql_framework'\n```\n\n## Usage\n\n### Environment Variables\n\n#### MySQL Connection Variables\n\n* `MYSQL_HOST` - MySQL Host\n* `MYSQL_PORT` - MySQL Port\n* `MYSQL_DATABASE` - MySQL database name\n* `MYSQL_USERNAME` - MySQL username\n* `MYSQL_PASSWORD` - MySQL password\n\n#### MySQL Timeout Variables\n\n* `MYSQL_READ_TIMEOUT` - how long before connections time out when reading information from the DB (default: `30` seconds)\n* `MYSQL_WRITE_TIMEOUT` - how long before connections time out when writing information to the DB (default: `10` seconds)\n\n#### MySQL Connection Pooling Variables\n\n* `MYSQL_START_POOL_SIZE` - how many connections should be created by default (default: `1`)\n* `MYSQL_MAX_POOL_SIZE` - how many connections should the pool be allowed to grow to (default: `5`)\n\n#### MySQL Migration Variables\n\n* `MYSQL_MIGRATION_TABLE` - the name of the table that holds a record of applied migrations (default: `migration_script_history`)\n* `MYSQL_MIGRATION_LOCK_TTL` - how long the tables should be locked for whilst performing migrations (default: `2000` / `2 seconds`)\n* `MYSQL_MIGRATION_LOCK_MAX_ATTEMPTS` - how many times the lock manager should attempt to acquire the lock before failing (default: `300`)\n* `MYSQL_MIGRATION_LOCK_RETRY_DELAY_S` - how long the lock manager should sleep between lock request attempts (default: `1 second`)\n* `REDIS_URL` - The URL for redis - used for managing locks for DB migrations\n\n#### Miscellaneous Variables\n\n* `MYSQL_PARTITIONS` - if a table is partitioned, how many partitions should be created (default: `500`)\n\n### Migration Scripts\n\nMigration scripts need to be in the following format:\n\n```ruby\nclass CreateDemoTable \u003c MysqlFramework::Scripts::Base\n  def initialize\n    @identifier = 201806021520 # 15:20 02/06/2018\n  end\n\n  def apply(client)\n   client.query(\u003c\u003c~SQL)\n      CREATE TABLE IF NOT EXISTS `#{table_name}` (\n        `id` CHAR(36) NOT NULL,\n        `name` VARCHAR(255) NULL,\n        `created_at` DATETIME NOT NULL,\n        `updated_at` DATETIME NOT NULL,\n        PRIMARY KEY (`id`)\n      )\n    SQL\n  end\n\n  def rollback(client)\n    client.query(\u003c\u003c~SQL)\n      DROP TABLE IF EXISTS `#{table_name}`\n    SQL\n  end\n\n  def tags\n    [table_name]\n  end\n\n  private\n\n  def table_name\n    DemoTable::NAME\n  end\nend\n```\n\n#### #initialize\n\nThe initialize method should set the `@identifier` value, which should be a timestamp:\n\n```ruby\n@identifier = 201806021520 # 15:20 02/06/2018\n```\n\nMake sure `@identifier` is an integer too, otherwise `MysqlFramework::Scripts::Manager` may struggle to determine which are your pending migrations.\n\n#### #apply\n\nThe `apply` method should action the migration. An instance of `Mysql2::Client` is\navailable as `client` to use.\n\n#### #rollback\n\nThe `rollback` method should action the migration. An instance of `Mysql2::Client` is\navailable as `client` to use.\n\n#### #tags\n\nTags are used for when we want to specify which migrations to run based on a tag. This is useful\nfor tests where you don't need to run all migrations to assert something is working or not.\n\n#### Running migrations\n\nUse the `MysqlFramework::Scripts::Manager#execute` method to run all pending migrations.\n\n### MysqlFramework::Scripts::Table\n\nUsed to register tables. This is used as part of the `all_tables` method in the script manager for\nawareness of tables to drop.\n\n```ruby\nclass DemoTable\n  extend MysqlFramework::Scripts::Table\n\n  NAME = 'demo'\n\n  register_table NAME\nend\n```\n\n### MysqlFramework::Connector\n\nThe connector deals with the connection pooling of `MySQL2::Client` instances, providing a wrapper for queries and transactions.\n\n```ruby\nconnector = MysqlFramework::Connector.new\nconnector.setup\nconnector.query(\u003c\u003c~SQL)\n  SELECT * FROM gems\nSQL\n```\n\nOptions can be provided to override the defaults as follows:\n\n```ruby\noptions = {\n  host: ENV.fetch('MYSQL_HOST'),\n  port: ENV.fetch('MYSQL_PORT'),\n  database: ENV.fetch('MYSQL_DATABASE'),\n  username: ENV.fetch('MYSQL_USERNAME'),\n  password: ENV.fetch('MYSQL_PASSWORD'),\n  reconnect: true\n}\nMysqlFramework::Connector.new(options)\n```\n\n#### #setup\n\nSets up the connection pooling. Creates `ENV['MYSQL_START_POOL_SIZE']` `Mysql2::Client` instances up front. This is provided as a separate method to allow for use within process forking where connections would need to be created after forking the process.\n\n```ruby\nconnector.setup\n```\n\n#### #dispose\n\nCloses all the `Mysql2::Client` connections and removes the connection pool. Intended as a clean-up method to be used on process fork shutdown.\n\n```ruby\nconnector.dispose\n```\n\n#### #check_out\n\nCheck out a client from the connection pool. Will create new `Mysql2::Client` instances up-to `ENV['MYSQL_MAX_POOL_SIZE']` times if no idle connections are available.\n\n```ruby\nclient = connector.check_out\n```\n\n#### #check_in\n\nCheck in a client to the connection pool\n\n```ruby\nclient = connector.check_out\n# ...\nconnector.check_in(client)\n```\n\n#### #with_client\n\nCalled with a block. The method checks out a client from the pool and yields it to the block. Finally it ensures that the client is always checked back into the pool.\n\n```ruby\nconnector.with_client do |client|\n  client.query(\u003c\u003c~SQL)\n    SELECT * FROM gems\n  SQL\nend\n```\n\nIt can optionally accept an existing client to avoid starting new connections in the middle of a transaction. This can be used to ensure that a series of queries are wrapped by the same transaction.\n\n```ruby\nconnector.with_client(existing_client) do |client|\n  client.query(\u003c\u003c~SQL)\n    SELECT * FROM gems\n  SQL\nend\n```\n\n#### #execute\n\nThis method is called when executing a prepared statement where value substitution is required:\n\n```ruby\ninsert = MysqlFramework::SqlQuery.new.insert(gems)\n  .into(gems[:id],gems[:name],gems[:author],gems[:created_at],gems[:updated_at])\n  .values(SecureRandom.uuid,'mysql_framework','sage',Time.now,Time.now)\n\nconnector.execute(insert)\n```\n\nIt can optionally accept an existing client to avoid checking out a new client.\n\n```ruby\ninsert = MysqlFramework::SqlQuery.new.insert(gems)\n  .into(gems[:id],gems[:name],gems[:author],gems[:created_at],gems[:updated_at])\n  .values(SecureRandom.uuid,'mysql_framework','sage',Time.now,Time.now)\n\nconnector.execute(insert, existing_client)\n```\n\n#### #query\n\nThis method is called to execute a query without having to worry about obtaining a client\n\n```ruby\nconnector.query(\u003c\u003c~SQL)\n  SELECT * FROM versions\nSQL\n```\n\nIt can optionally accept an existing client to avoid checking out a new client.\n\n```ruby\nconnector.query(\u003c\u003c~SQL, existing_client)\n  SELECT * FROM versions\nSQL\n```\n\n#### #transaction\n\nThis method requires a block and yields a client obtained from the pool. It wraps the yield in a `BEGIN` and `COMMIT` query. If an exception is raised then it will submit a `ROLLBACK` query and re-raise the exception.\n\n```ruby\ninsert = MysqlFramework::SqlQuery.new.insert(gems)\n  .into(gems[:id],gems[:name],gems[:author],gems[:created_at],gems[:updated_at])\n  .values(SecureRandom.uuid,'mysql_framework','sage',Time.now,Time.now)\n\nconnector.transaction do |client|\n  client.query(insert)\nend\n```\n\n#### #default_options\n\nThe default options used to initialise MySQL2::Client instances:\n\n```ruby\n{\n  host: ENV.fetch('MYSQL_HOST'),\n  port: ENV.fetch('MYSQL_PORT'),\n  database: ENV.fetch('MYSQL_DATABASE'),\n  username: ENV.fetch('MYSQL_USERNAME'),\n  password: ENV.fetch('MYSQL_PASSWORD'),\n  reconnect: true\n}\n```\n\n### MysqlFramework::SqlCondition\n\nA representation of a MySQL Condition for a column. Created automatically by SqlColumn\n\n```ruby\n# eq condition\nSqlCondition.new(column: 'name', comparison: '=', value: 'mysql_framework')\n```\n\n### MysqlFramework::SqlColumn\n\nA representation of a MySQL column within a table. Created automatically by SqlTable.\n\n```ruby\nSqlCondition.new(table: 'gems', column: 'name')\n```\n\n### MysqlFramework::SqlQuery\n\nA representation of a MySQL Query.\n\n```ruby\ngems = MysqlFramework::SqlTable.new('gems')\nguid = SecureRandom.uuid\n\n# Insert Query\ninsert = MysqlFramework::SqlQuery.new.insert(gems)\n  .into(gems[:id],gems[:name],gems[:author],gems[:created_at],gems[:updated_at])\n  .values(guid,'mysql_framework','sage',Time.now,Time.now)\n\n# Update Query\nupdate = MysqlFramework::SqlQuery.new.update(gems)\n  .set(updated_at: Time.now)\n  .where(gems[:id].eq(guid))\n\n# Delete Query\ndelete = MysqlFramework::SqlQuery.new.delete\n  .from(gems)\n  .where(gems[:id].eq(guid))\n\n# Bulk Values Query\nbulk_insert = MysqlFramework::SqlQuery.new.insert(gems)\n  .into(gems[:id],gems[:name],gems[:author],gems[:created_at],gems[:updated_at])\n  .bulk_values([[guid,'mysql_framework','sage',Time.now,Time.now], [guid,'mysql_framework','sage',Time.now,Time.now]])\n\n# Bulk On Duplicate Query\nbulk_upsert = MysqlFramework::SqlQuery.new.insert(gems)\n  .into(gems[:id],gems[:name],gems[:author],gems[:created_at],gems[:updated_at])\n  .bulk_values([[guid,'mysql_framework','sage',Time.now,Time.now], [guid,'mysql_framework','sage',Time.now,Time.now]])\n  .on_duplicate(gems[:id] =\u003e nil,gems[:name] =\u003e nil,gems[:author] =\u003e nil,gems[:created_at] =\u003e nil,gems[:updated_at] =\u003e nil)\n```\n\n### MysqlFramework::SqlTable\n\nA representation of a MySQL table.\n\n```ruby\nMysqlFramework::SqlTable.new('gems')\n```\n\n### Configuring Logs\n\nAs a default, `MysqlFramework` will log to `STDOUT`. You can provide your own logger using the `logger=` method:\n\n```ruby\nMysqlFramework.logger = Logger.new('development.log')\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/sage/mysql_framework. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n## Testing (with Docker)\nA compose file is provided for running specs.\n\n### Setup\n```\ndocker-compose up -d\ndocker-compose exec test-runner bash\n# Once the shell opens in the container\nbundle\n```\n\n### Running specs\n```\nbundle exec rspec\n```\nExit out of the shell when finished.\n\n### Cleanup\n```\ndocker-compose down\n```\n\n## License\n\nThis gem is available as open source under the terms of the [MIT licence](LICENSE).\n\nCopyright (c) 2018 Sage Group Plc. All rights reserved.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsage%2Fmysql_framework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsage%2Fmysql_framework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsage%2Fmysql_framework/lists"}