{"id":15107715,"url":"https://github.com/pegesund/heysql","last_synced_at":"2025-06-18T18:04:48.116Z","repository":{"id":53996185,"uuid":"209078516","full_name":"pegesund/heysql","owner":"pegesund","description":"Sql-based orm based on smalltalk reflection ideas","archived":false,"fork":false,"pushed_at":"2022-03-30T15:19:17.000Z","size":126,"stargazers_count":20,"open_issues_count":1,"forks_count":6,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-06-18T18:04:26.980Z","etag":null,"topics":["pharo","pharo-smalltalk"],"latest_commit_sha":null,"homepage":null,"language":"Smalltalk","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/pegesund.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}},"created_at":"2019-09-17T14:36:25.000Z","updated_at":"2025-04-02T23:54:36.000Z","dependencies_parsed_at":"2022-08-13T05:50:40.314Z","dependency_job_id":null,"html_url":"https://github.com/pegesund/heysql","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/pegesund/heysql","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pegesund%2Fheysql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pegesund%2Fheysql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pegesund%2Fheysql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pegesund%2Fheysql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pegesund","download_url":"https://codeload.github.com/pegesund/heysql/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pegesund%2Fheysql/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260606454,"owners_count":23035349,"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":["pharo","pharo-smalltalk"],"created_at":"2024-09-25T21:41:12.463Z","updated_at":"2025-06-18T18:04:43.099Z","avatar_url":"https://github.com/pegesund.png","language":"Smalltalk","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HeySql - a mini-orm for Pharo Smalltalk\n\nVesion 1.3\n\n## Reason\n\nThe older I have got, the more I have started to dislike database orms. They are often hard to migrate, they are hard to debug, and they put not needed pressure on the db with insane long joins for doing pretty simple stuff (oh, yes - JPA - I am talking about you!).\n\nOn the other hand one very often do have something like models in the code. Product, customer, purchase and so on - and the boilerplate to get this stuff up and running can be quite boring and time consuming.\n\nHeySql is an attempt to find the right balance between an orm and doing things in sql.\n\n\n## Usage\n\nThe code is based upon the awesome P3-library for Postgresql.\n\n### This code has been written in Pharo Smalltalk\n\n\t- Uses reflection\n\t- Adds methods by compiling in runtime (very nice language feature!)\n\t- Uses the standard test-system. To run the tests in the test-package you must set up the database propery on your local computer.\n\nIf you are intrested in the magnificent smalltalk language, you can read more about my experience writing the library here: https://ramblings.work/posts/2019-02-10-heysql.html.\n\n### Connecting\n\n```smalltalk\nclient := P3Client new url: 'psql://test@localhost'.\nHeySql connect: client\nHeySql init\n```\n\n### Automaticall generated functionality\n\nAfter creating the class, you can just write this piece of code (say you have a class called Person with object vars forname and surname). \"person\" is the name of the db-table.\n\n\n\nYou will now have this functionality for the class:\n\n\t- Getter and setters\n\t- The object methods insert and update\n\nSo you can do:\n\n``` smalltalk\nperson := Person new.\nperson forname: 'peter'.\nperson insert.\n```\n\nAnd the object will be stored in the database. Same goes for update.\n\n### Specifying which instance side variable which have counterparts in the database table\n\ndbFields can be used to specify which fields that should be used - if not  default is to use all fields as db-fields\n\nThe dbFields method must be called before the first insert/updates functions are used, in case not it will not have an impact.\n\n```smalltalk\n\tPerson dbFields: 'forname surname'.\n```\n\n### Subclassing models\n\nSubclassing for example Person and write an AdminPerson in your models, will work fine.\n\nThe fields defined with dbFields: '...' in persons will be inherited and created as columns in AdminPerson.\n\nIf you do not define a dbFields for a class, all instance variables will be used as database columns, included the inherited onces.\n\n\n### Adding your own functions\n\nIt is very easy to add your own queries as well.\n\n```smalltalk\n\tdict := Dictionary\n\t\tnewFrom:\n\t\t\t{('personsFindall' -\u003e 'select * from person').\n\t\t\t('personsFindByForename, surname'\n\t\t\t\t-\u003e 'select * from person where forname = $1 and surname = $2').\n\t\t\t('byId' -\u003e 'select * from person where id = $1')}.\n\tPerson generateSqlMethods: dict.\n```\n\nYou will now have access to the new defined methods.\n\nNote that the number of $'s must match the number of string separated functions on the left hand side in the dictionary!\n\nThe return of a generated query will be the objects of the defined type, in this case of person-type. If we get one result, this is returned - if we get several the result is an array.\n\n```smalltalk\npersons := Person personsFindall.\nperson := Person byId: 1.\npersons := Person personsFindByForename: 'petter' surname: 'egesund'\n```\n\nDoing queries this way has these advantages:\n\n\t- You have the full power of sql, no strange dsl\n\t- Sql-queries will available as code methods, with parameteres and available in autocomplete. I like prefixing all queries for the person-class with person.. to easy look up methods for the class.\n\t- This should make the code more readable and be a good starting point for reusing queries.\n\t- It should be pretty easy to use all the features of the database, ex. json, gis, freetext search and so on - which normally not are available in orms.\n\t- As we use server side compiled statemens it sould be pretty fast and also safe when it comes to hijacking.\n\n### Creating tables\n\nYou can use this helper function to create tables, or you can do it your way:\n\n```smalltalk\npersonTable := {('id' -\u003e 'serial').\n\t('forname' -\u003e 'text').\n\t('surname' -\u003e 'text')} asDictionary.\n\tHeySql createTable: 'person' tableDict: personTable\n```\n\nAll data types which are supported by P3 will work fine. Note that this one drops your table if found from before!\n\n### Database migration\n\nEvery projects seems starts with a couple of models and the attitude at the beginning is that these will not change much.\n\nWell, in reality - they will - for sure.\n\nTherefore a good way to handle database changes should be at at the core of every system.\n\nMigrations in HeySql is based on generating methods in an migration object.\n\nFirst thing is to create your class, that will hold the migrations, ex. MyMigrations.\n\nThen tell HeySql that this class will hold the migrations:\n\n```smalltalk\nHeySqlDbMigrator new: MyMigrations.\n```\n\nEach method will have the timestamp of the creation time as its name.\n\nAfter this you can create class methods in MyMirations with this three functions:\n\n- HeySqlDbMigrator createMigration\n\nThis creates an empty method, ready to be filled in with code.\n\nexample:\n\n```smalltalk\ncon := HeySql connection.\ncon execute: 'create table, to something sql'\n```\n\n- HeySqlDbMigrator createMigration ClassName\n\nIf you have used used the methods \"dbFields: \"a b c\" these use these in the template creation. Otherwise it will use all instance variables as possible database fields.\n\nThe method creates a template of this form (like above), based on the class:\n\n```smalltalk\npersonTable := {('id' -\u003e 'serial primary key').\n\t('forname' -\u003e 'type').\n\t('surname' -\u003e 'type') .\n\t('companyId' -\u003e integer references company(id)').\n\t('createdDate' -\u003e 'timestamp')\n\t} asDictionary.\n```\n\nIn the template you must change all 'type' to real postgres types, example integer or text.\n\nAs in the example variable name of type id, or endsWithId or endsWithDate gives predefined types.\n\nAll types can be overwritten as wished.\n \n- HeySqlDbMigrator createMigrationPackage\n\nif you run for example \n\n```smalltalk\nHeySqlDbMigrator createMigrationPackage 'MyPackage-Models'\n```\n\nthere will be created templates for all classes in the package.\n\nThis might be another good reason for keeping the models in a separate package.\n\n\n### Running the migrations\n\nSimply run HeySqlDbMigrator migrate\n\nA new table will be created in your database, if not this does not already exist.\n\nThis table will keep information about last migration date and when you are ready to run new migrations just rerun this method.\n\nThis migration runs inside a transaction, so either all methods will be executed or none, if any error encounted.\n\n\n### Example usage\n\nThis part taken from the tests should illustrate usage of most funcionality.\n\n```smalltalk\ntestSqlMethodsCreated\n\t\"check that insert works and that it returns correct new id. check that correct sql statements are created for the different methods, and that these give correct result\"\n\n\t| dict person person2 person3 |\n\tHeySql init.\n\tPerson dbFields: 'forname surname'.\n\tperson := Person new.\n\tperson forname: 'petter'.\n\tperson surname: 'egesund'.\n\tperson insert.\n\tself assert: [ person id == 1 ].\n\tperson2 := Person new.\n\tperson2 forname: 'petter2'.\n\tperson2 surname: 'egesund2'.\n\tperson2 insert.\n\tself assert: [ person2 id == 2 ].\n\tdict := Dictionary\n\t\tnewFrom:\n\t\t\t{('personsFindall' -\u003e 'select * from person').\n\t\t\t('personsFindByForename, surname'\n\t\t\t\t-\u003e 'select * from person where forname = $1 and surname = $2').\n\t\t\t('byId' -\u003e 'select * from person where id = $1')}.\n\tPerson generateSqlMethods: dict.\n\tself assert: [ Person personsFindall size == 2 ].\n\tself\n\t\tassert: [ (Person personsFindByForename: 'petter' surname: 'egesund')\n\t\t\t\tisKindOf: lPerson ].\n\tperson forname: 'hans petter'.\n\tperson update.\n\tperson3 := Person byId: 1.\n\tself assert: [ person3 id = 1 ].\n\tself assert: [ person3 forname = 'hans petter' ]\n```\n\n\n### Implementation and pitfalls\n\n\t- Still in version 1.1\n\t- Variables in the classes must have the exact same name as in the datbase. I consider this as a good coding style and as a feature.\n\t- I do not parse the sql, but use some simple regexps. Normally this should not be a problem, but if your queries due to some strange reasons contains $NUM you might get into trouble. Values to be inserted can off course contain these special characters.\n\t- These methods does actually generate and compile code for you. If you owerwrite these methods and rerun the generation methods your code will be overwritten.\n\t- Due to the compile-edit-lifecycle in the gui, you must run the generators before the code is accepted when coding. I use the playground or you can even move the models to a separate package - the models can then be genereatet from the baseline with the #postLoadDoIt function. There are probably many other ways to handle this as well.\n\n### Code loading\n\nLoad code like this. Will Load P3 as well.\n\n```smalltalk\nMetacello new\n   baseline: 'HeySql';\n   repository: 'github://pegesund/heysql';\n   load.\n```\n\n## License and usage\n\nThis is MIT-license, use it as you would like.\n\nDrop me a line if you use the library for anything - would be cool to know!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpegesund%2Fheysql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpegesund%2Fheysql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpegesund%2Fheysql/lists"}