All Projects → pegesund → heysql

pegesund / heysql

Licence: other
Sql-based orm based on smalltalk reflection ideas

Programming Languages

smalltalk
420 projects

Projects that are alternatives of or similar to heysql

protobuf-smalltalk
Protocol buffers support for Smalltalk
Stars: ✭ 14 (-26.32%)
Mutual labels:  pharo, pharo-smalltalk
Moose2Model
A software exploration tool to support developers during their work
Stars: ✭ 12 (-36.84%)
Mutual labels:  pharo, pharo-smalltalk
iPharo
Pharo Smaltalk kernel for Jupyter
Stars: ✭ 32 (+68.42%)
Mutual labels:  pharo, pharo-smalltalk
kendrick
Domain-Specific Modeling for Epidemiology
Stars: ✭ 43 (+126.32%)
Mutual labels:  pharo, pharo-smalltalk
libtensorflow-pharo-bindings
TensorFlow library bindings for Pharo
Stars: ✭ 30 (+57.89%)
Mutual labels:  pharo, pharo-smalltalk
NEAT
NEAT implementation in Pharo
Stars: ✭ 16 (-15.79%)
Mutual labels:  pharo, pharo-smalltalk
SmalltalkVimMode
Vim Mode for Playground, System Browser, Debugger in Pharo.
Stars: ✭ 39 (+105.26%)
Mutual labels:  pharo, pharo-smalltalk
Winter
Winter is a 2D game engine for Pharo Smalltalk
Stars: ✭ 43 (+126.32%)
Mutual labels:  pharo, pharo-smalltalk
Cruiser
A Pharo Tool to package applications
Stars: ✭ 41 (+115.79%)
Mutual labels:  pharo, pharo-smalltalk
Grease
The Grease Portability Library
Stars: ✭ 12 (-36.84%)
Mutual labels:  pharo, pharo-smalltalk
PharoJS
PharoJS: Develop in Pharo, Run on JavaScript
Stars: ✭ 90 (+373.68%)
Mutual labels:  pharo, pharo-smalltalk
Teapot
Teapot micro web framework for Pharo Smalltalk
Stars: ✭ 86 (+352.63%)
Mutual labels:  pharo, pharo-smalltalk
PharoPDS
Probabilistic data structures in Pharo Smalltalk.
Stars: ✭ 28 (+47.37%)
Mutual labels:  pharo-smalltalk
GitBridge
Access resources and information from the git repository containing your project.
Stars: ✭ 14 (-26.32%)
Mutual labels:  pharo
awesome-pharo-ml
List of projects, books, booklets, papers, and applications related to machine learning, AI, data science in Pharo
Stars: ✭ 56 (+194.74%)
Mutual labels:  pharo
msgpack-smalltalk
MessagePack serialization library for various Smalltalk dialects / msgpack.org[Smalltalk]
Stars: ✭ 22 (+15.79%)
Mutual labels:  pharo
MatplotLibBridge
A bridge to provide the ability to Pharo user to use Python's Matplotlib.
Stars: ✭ 20 (+5.26%)
Mutual labels:  pharo
gt4gemstone-old
The Glamorous Toolkit for remote work with Gemstone/S
Stars: ✭ 14 (-26.32%)
Mutual labels:  pharo
Gratch
Block-style programming environment for tackling graph structure and graph algorithm, based on MIT Scratch.
Stars: ✭ 15 (-21.05%)
Mutual labels:  pharo
Willow
The Web Interaction Library that eases the burden of creating AJAX-based web applications
Stars: ✭ 41 (+115.79%)
Mutual labels:  pharo

HeySql - a mini-orm for Pharo Smalltalk

Vesion 1.3

Reason

The 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!).

On 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.

HeySql is an attempt to find the right balance between an orm and doing things in sql.

Usage

The code is based upon the awesome P3-library for Postgresql.

This code has been written in Pharo Smalltalk

- Uses reflection
- Adds methods by compiling in runtime (very nice language feature!)
- Uses the standard test-system. To run the tests in the test-package you must set up the database propery on your local computer.

If 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.

Connecting

client := P3Client new url: 'psql://test@localhost'.
HeySql connect: client
HeySql init

Automaticall generated functionality

After 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.

You will now have this functionality for the class:

- Getter and setters
- The object methods insert and update

So you can do:

person := Person new.
person forname: 'peter'.
person insert.

And the object will be stored in the database. Same goes for update.

Specifying which instance side variable which have counterparts in the database table

dbFields can be used to specify which fields that should be used - if not default is to use all fields as db-fields

The dbFields method must be called before the first insert/updates functions are used, in case not it will not have an impact.

	Person dbFields: 'forname surname'.

Subclassing models

Subclassing for example Person and write an AdminPerson in your models, will work fine.

The fields defined with dbFields: '...' in persons will be inherited and created as columns in AdminPerson.

If you do not define a dbFields for a class, all instance variables will be used as database columns, included the inherited onces.

Adding your own functions

It is very easy to add your own queries as well.

	dict := Dictionary
		newFrom:
			{('personsFindall' -> 'select * from person').
			('personsFindByForename, surname'
				-> 'select * from person where forname = $1 and surname = $2').
			('byId' -> 'select * from person where id = $1')}.
	Person generateSqlMethods: dict.

You will now have access to the new defined methods.

Note that the number of $'s must match the number of string separated functions on the left hand side in the dictionary!

The 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.

persons := Person personsFindall.
person := Person byId: 1.
persons := Person personsFindByForename: 'petter' surname: 'egesund'

Doing queries this way has these advantages:

- You have the full power of sql, no strange dsl
- 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.
- This should make the code more readable and be a good starting point for reusing queries.
- 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.
- As we use server side compiled statemens it sould be pretty fast and also safe when it comes to hijacking.

Creating tables

You can use this helper function to create tables, or you can do it your way:

personTable := {('id' -> 'serial').
	('forname' -> 'text').
	('surname' -> 'text')} asDictionary.
	HeySql createTable: 'person' tableDict: personTable

All data types which are supported by P3 will work fine. Note that this one drops your table if found from before!

Database migration

Every projects seems starts with a couple of models and the attitude at the beginning is that these will not change much.

Well, in reality - they will - for sure.

Therefore a good way to handle database changes should be at at the core of every system.

Migrations in HeySql is based on generating methods in an migration object.

First thing is to create your class, that will hold the migrations, ex. MyMigrations.

Then tell HeySql that this class will hold the migrations:

HeySqlDbMigrator new: MyMigrations.

Each method will have the timestamp of the creation time as its name.

After this you can create class methods in MyMirations with this three functions:

  • HeySqlDbMigrator createMigration

This creates an empty method, ready to be filled in with code.

example:

con := HeySql connection.
con execute: 'create table, to something sql'
  • HeySqlDbMigrator createMigration ClassName

If 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.

The method creates a template of this form (like above), based on the class:

personTable := {('id' -> 'serial primary key').
	('forname' -> 'type').
	('surname' -> 'type') .
	('companyId' -> integer references company(id)').
	('createdDate' -> 'timestamp')
	} asDictionary.

In the template you must change all 'type' to real postgres types, example integer or text.

As in the example variable name of type id, or endsWithId or endsWithDate gives predefined types.

All types can be overwritten as wished.

  • HeySqlDbMigrator createMigrationPackage

if you run for example

HeySqlDbMigrator createMigrationPackage 'MyPackage-Models'

there will be created templates for all classes in the package.

This might be another good reason for keeping the models in a separate package.

Running the migrations

Simply run HeySqlDbMigrator migrate

A new table will be created in your database, if not this does not already exist.

This table will keep information about last migration date and when you are ready to run new migrations just rerun this method.

This migration runs inside a transaction, so either all methods will be executed or none, if any error encounted.

Example usage

This part taken from the tests should illustrate usage of most funcionality.

testSqlMethodsCreated
	"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"

	| dict person person2 person3 |
	HeySql init.
	Person dbFields: 'forname surname'.
	person := Person new.
	person forname: 'petter'.
	person surname: 'egesund'.
	person insert.
	self assert: [ person id == 1 ].
	person2 := Person new.
	person2 forname: 'petter2'.
	person2 surname: 'egesund2'.
	person2 insert.
	self assert: [ person2 id == 2 ].
	dict := Dictionary
		newFrom:
			{('personsFindall' -> 'select * from person').
			('personsFindByForename, surname'
				-> 'select * from person where forname = $1 and surname = $2').
			('byId' -> 'select * from person where id = $1')}.
	Person generateSqlMethods: dict.
	self assert: [ Person personsFindall size == 2 ].
	self
		assert: [ (Person personsFindByForename: 'petter' surname: 'egesund')
				isKindOf: lPerson ].
	person forname: 'hans petter'.
	person update.
	person3 := Person byId: 1.
	self assert: [ person3 id = 1 ].
	self assert: [ person3 forname = 'hans petter' ]

Implementation and pitfalls

- Still in version 1.1
- 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.
- 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.
- 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.
- 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.

Code loading

Load code like this. Will Load P3 as well.

Metacello new
   baseline: 'HeySql';
   repository: 'github://pegesund/heysql';
   load.

License and usage

This is MIT-license, use it as you would like.

Drop me a line if you use the library for anything - would be cool to know!

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].