I am currently dealing with problem of how to pass specific API implementation to some module. To be more precise I am working on ORM for Nim that can use any of db_sqlite, db_mysql or db_postgres since anyway they all share same API, but are different modules of course.
However currently it is not possible to "inject" some module into some other module, so I need to use some nasty trick using compiler defines in my orm module:
when defined(sqlite):
from db_sqlite as db
elif defined(postgres):
from db_postgres as db
elif defined(mysql):
from db_mysql as db
However it would be much better if module itself could assume that db namespace is just injected by the importer, eg.:
import db_sqlite as db
import orm using db
Or:
import db_sqlite
import orm using db_sqlite as db
Since using is existing keyword I think it fits best this particular case.
Also it would be very useful to have some namespaceExists compile time proc/macro to:
when not namespaceExist(db):
error("please pass db API to orm with: import orm using db, where db is specific db API imported before")
WDYT?
Why not just binding to the backend dynamically?
currentORM = newOrm(newMySqlConnection("localhost"))
or smth like that?
also you can just include the orm module, i guess...
Why not just binding to the backend dynamically?
Because the orm module don't know anything about newMySqlConnection return type, unless it imports mysql. And this is what I am trying to avoid, because orm would need to import any possible module and handle every possible db connection type.
In theory we could make TDbConn type abstract, and use dynamic dispatch via method for exec or rows, but in practice nobody will use two backends at same time - so having dynamic dispatch overhead will be simply waste of CPU resources.
We could also do it making TDbConn type, exec proc and rows iterator "extern", but Nim language does not AFAIK have anything like C "extern".
Ok, have you tried including instead of importing? I think it should do exactly what you want:
type db = MySQLConnection
include orm
I am sorry, I was too lame to properly read you were proposing to use include not import. (previous answer)
Corrected answer - I don't think it is good idea at all to use include, imagine that orm will be NOT really small piece of code, and having many model modules including orm will lead to terrible code duplication and binary size.
Also the purpose of include, if I understand it correctly, it split huge modules into smaller code chunks.
# main.nim
type strong = distinct string
import module
proc strong1(s: strong) = echo "strong1"
# module.nim
proc strong2(s: strong) = echo "strong2"
Compiling main.nim leads to: module.nim:3:17: Error: undeclared identifier: 'strong', because module cannot see anything declared in scope of main.Okay, but my point is that this is rather easy to do it in plain C without all vtables and dynamic dispatch. Since anyway we are not going to use multiple DB interfaces at same time there is no point to do it dynamic way. Actually the reason I am working now with Nim is that it uses things when they're necessary, such as prefers doing stuff at compile time when it is possible, not use dynamic dispatch, if compile-time dispatch (overload) is sufficient, etc. etc.
Problem is that we cannot forward declare proc/iterator/type interfaces as external, something that is easy in C:
struct TDBConn; /* anonymous struct */
typedef struct TDBConn* DBConn;
void exec(DBConn *db, const char *query);
Simple as that. And I want use the same in Nim.
I would go the dynamic way too. IMHO its too simplistic assuming only one connection to db. Even for same db type What if you need to use multiple connections (thinking a multi divisional company with several databases for example) ? Its not that rare
I would prefer ORMConnection.User.where( ...
I don't see how C's way to do forward declaring of types has to do with anything here. What you want is module parametrization which we don't have:
import orm(db_sqlite)
The workaround is:
import orm_db_sqlite
The orm_db_sqlite, orm_db_mysql etc modules simply look like this:
# Module orm_db_sqlite
import db_sqlite
include orm_impl
# Module orm_db_mysql
import db_mysql
include orm_impl
gokhan: I would prefer ORMConnection.User.where(...
You mean rather: db.User.where(... because it is just db handle, not an ORM instance - ORM will have no instance, it is just a higher level abstraction layer over existing database API. Also note the lowercase db at it is a variable, not a type.