A couple of people have expressed interest in doing this, but is
anybody actively working on a Postgres adapter for ErlyDB? (I have
recently grown more interested in Postgres due to 2-phase commits and
better multi-cpu utilization than MySQL.)
Regards,
Yariv
I'm not currently working on any PostgreSQL adapter.. but the lack of
one is the primary reason I haven't tried out erlyweb. After several
bad experiences with MySQL, I'm no longer interested in writing new
software that has anything to do with it. I've also grown quite used
to optimizing PostgreSQL and its type system.
FWIW, PostgreSQL also solves your "licensing issue" with the repository.
-bob
I was initially interested in MySQL because of the built-in clustering
and replication features, which Postgres only has as add-ons of
varying quality. However, with 2 phase commits, you can (relatively)
easily add synchronous replication logic into the driver's connection
pool manager.
Yariv
I have no idea what the quality/status is of any Erlang PostgreSQL
driver.. I do all of my DB stuff with Python and send it to Erlang
over HTTP (the results aren't very volatile).
-bob
Having said that, I didn't notice anything in the Google Code terms of
service that explicitly forbids including libraries that have
different licenses in a project's repository, so maybe I can bring
the MySQL driver code into the ErlyWeb repository without upsetting
Google too much...
I started work on an adapter using the driver from Erlang Consulting. I
have not looked at it in a few days because of certain other situations
have come up, but I plan to continue hacking on it when I get a chance.
Regards,
Yariv
Roberto
http://www.pushon.org/erlydb_psql.erl
It is not complete as of yet. I am planning on hacking on it later
tonight.
Brian
Roberto
Roberto
On Jan 17, 2:39 am, "Yariv Sadan" <yari...@gmail.com> wrote:
> You can do it by setting up the svn:external property. It doesn't
> import the code but it sets a pointer to the external repository,
> which is then downloaded when you do 'svn update'.
>
Yariv
On 1/17/07, Roberto Saccon <rsa...@gmail.com> wrote:
>
When starting psql I get this:
2> application:start(psql).
ok
3> Event: {psql,parameter_status,
<<99,108,105,101,110,116,95,101,110,99,111,100,105,110,103,0,85,84,70,56,0>>}
Event: {psql,parameter_status,
<<68,97,116,101,83,116,121,108,101,0,73,83,79,44,32,77,68,89,0>>}
Event: {psql,parameter_status,
<<105,110,116,101,103,101,114,95,100,97,116,101,116,105,109,101,115,0,111,110,0>>}
Event: {psql,parameter_status,
<<105,115,95,115,117,112,101,114,117,115,101,114,0,111,102,102,0>>}
Event: {psql,parameter_status,
<<115,101,114,118,101,114,95,101,110,99,111,100,105,110,103,0,85,84,70,56,0>>}
Event: {psql,parameter_status,
<<115,101,114,118,101,114,95,118,101,114,115,105,111,110,0,56,46,49,46,52,0>>}
Event: {psql,parameter_status,
<<115,101,115,115,105,111,110,95,97,117,116,104,111,114,105,122,97,116,105,111,110,0,114,115,97,99,99,111,110,0>>}
Event: {psql,parameter_status,
<<115,116,97,110,100,97,114,100,95,99,111,110,102,111,114,109,105,110,103,95,115,116,114,105,110,103,115,0,111,102,102,0>>}
Event: {psql,parameter_status,
<<84,105,109,101,90,111,110,101,0,65,109,101,114,105,99,97,47,70,111,114,116,97,108,101,122,97,0>>}
Event: {psql,backend_key_data,<<0,0,49,155,116,176,249,226>>}
Any further attempts for actual queries with psql or Brian's
erlydb_psql hang forever or fail with weired error messages, so I
guess it is some initial authorization problem. Anybody an idea how to
get a human readable error report from this psql driver ?
Roberto
Thanks. I hope the code that I wrote will help. I just haven't had the
time to sit and concentrate on it.
Brian
Yariv (and who else is familiar with this kind of stuff), the erlang-
consulting driver is a full fledged OTP app and db name / db
authentication credentials are stored in in the *.app file. The mysql
driver is just server which gets started with start_link and db name /
db authentication credentials are passed in as options.
What is the way to go ? Leave it as is ? De-OTP-ify the postgres
driver, or is it planned to OTP-ify the mysql driver ?
Roberto
One value in OTP is its configuration file support. The reason I
mention this is that I think it makes sense to utilize it more in
ErlyWeb in general. Bringing ErlyWeb closer to OTP will make it easier
to configure something like the postgres driver, and potentially, any
other driver that might come up, but you also get a general
configuration file support already available to us. :)
Brian
I think the ultimate solution is to take only the code for
implementing the Postgres protocol and plug it in the backend of the
MySQL dispatcher. This would provide a single connection pooling and
transaction handling mechanism for both drivers (I believe it would
also be more efficient, because AFAIK the Postgres driver relies on an
extra process to manage transaction resources, which is redundant --
it's better to just send the transaction Fun to the connection process
that executes it). However, this would require more work to pull
off...
Yariv
Driver:get_last_insert_id(Options)
to
Driver:get_last_insert_id(Table, Options)
and provide patch erlydb_base and erlydb_mysql to work with this new
signature. Is that o.k. ?? or is there better approach for this ?
Roberto
Yariv
Does erlydb know which table was inserted to last? Maybe it should
keep that state around so that it could throw an error if a MySQL user
passes in a table name that wasn't the absolute last table modified...
Better to throw an error than return an invalid number from some other
table, which you might not even catch if you were using constraints
(MySQL has those right?).
One could also use that state to allow the zero argument version for
pgsql... but as a PostgreSQL user I'd still prefer the explicit
version.
-bob
(and my postgres driver is not working yet, currently erlydb_base
crashes on do_save and lots of other problems ...)
On Feb 26, 1:49 pm, "Yariv Sadan" <yari...@gmail.com> wrote:
> ErlyDB always calls last_insert_id() immediately following an INSERT
> and inside a BEGIN ... COMMIT block. I believe this guarantees that
> the result of last_insert_id() is always what you expect. If not, this
> needs to be fixed ASAP.
>
> On 2/25/07, Bob Ippolito <b...@redivi.com> wrote:
>
>
>
> > Semantically that's a bit off though, isn't it? PostgreSQL lets you
> > get the last insert id for any table at any time so long as there was
> > an insert to that table during that transaction (by observing the
> > value of the sequence).
>
> > Does erlydb know which table was inserted to last? Maybe it should
> > keep that state around so that it could throw an error if a MySQL user
> > passes in a table name that wasn't the absolute last table modified...
> > Better to throw an error than return an invalid number from some other
> > table, which you might not even catch if you were using constraints
> > (MySQL has those right?).
>
> > One could also use that state to allow the zero argument version for
> > pgsql... but as a PostgreSQL user I'd still prefer the explicit
> > version.
>
> > -bob
>
> > On 2/25/07, Yariv Sadan <yari...@gmail.com> wrote:
>
> > > Sounds good -- the mysql driver will just ignore the Table parameter.
>
> > > Yariv
>
when trying to access certain fields with
myModel:myField(myModule)
it issues an erlydb_base:get/2 command with an index out of range
but if I just list the fields (e.g.: myModel:fields()) everthing looks
fine.
In case you encountered that kind of problem, while developing erlydb,
and have some hints ..
regards
Roberto
The BSD-licensed Erlang-consulting driver I have put at
http://code.google.com/p/erlang-psql-driver/ so it can be included at
erlydb via SVN external. (Yariv, do you want me to add you there as
project owner, project member or not at all ?)
erlydb_psql basically works, but there are some major issues (such as
TEXT / VARCHARS returned as strings and not as binaries by the
driver). Next I plan to modify the Erlang-consulting driver, following
the previously in this thread suggested steps, to sort out those
issues. But before approaching that i would like to discuss what
exactly can be consolidated / refactored out, so it can be used for
any SQL-database:
- renaming mysql.erl to erlydb_dispatcher.erl
- renaming mysql.hrl to erlydb.hrl
(and change #mysql_result to #erlydb_result)
Anything else ? Other thoughts ?
Roberto
Patch:
------
Index: /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/erlydb.erl
===================================================================
--- /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/erlydb.erl
(revision 104)
+++ /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/erlydb.erl
(working copy)
@@ -128,6 +128,7 @@
driver_mod(mysql) -> erlydb_mysql;
+driver_mod(psql) -> erlydb_psql;
driver_mod(mnesia) -> erlydb_mnesia;
driver_mod(odbc) -> erlydb_odbc.
Index: /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/
erlydb_base.erl
===================================================================
--- /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/erlydb_base.erl
(revision 104)
+++ /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/erlydb_base.erl
(working copy)
@@ -1439,7 +1439,7 @@
HasIdentity = erlydb_field:extra(PkField)
== identity,
if HasIdentity ->
- case DriverMod:get_last_insert_id(
+ case DriverMod:get_last_insert_id(db_table(Module),
Options) of
{ok, Val} ->
FName = erlydb_field:name(
@@ -1448,7 +1448,7 @@
Err ->
Err
end;
- true ->
+ true ->
Rec1
end;
Err ->
Index: /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/
erlydb_field.erl
===================================================================
--- /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/
erlydb_field.erl (revision 104)
+++ /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/
erlydb_field.erl (working copy)
@@ -182,10 +182,11 @@
Field#erlydb_field.extra.
get_erl_type({Type, _Len}) -> get_erl_type(Type);
-get_erl_type(Type) ->
+get_erl_type(Type) ->
case Type of
varchar -> binary;
char -> binary;
+ 'character varying' -> binary;
binary -> binary;
varbinary -> binary;
blob -> binary;
@@ -204,6 +205,7 @@
datetime -> datetime;
date -> date;
timestamp -> datetime;
+ 'timestamp without time zone' -> datetime;
time -> time;
year -> integer;
Other -> Other
@@ -227,6 +229,7 @@
get_html_binary_input_type(Type) ->
case Type of
varchar -> text_field;
+ 'character varying' -> text_field;
char -> text_field;
binary -> text_field;
varbinary -> text_field;
Index: /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/
erlydb_mysql.erl
===================================================================
--- /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/
erlydb_mysql.erl (revision 104)
+++ /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/
erlydb_mysql.erl (working copy)
@@ -28,7 +28,7 @@
select/2,
select_as/3,
update/2,
- get_last_insert_id/1,
+ get_last_insert_id/2,
prepare/2,
execute/2,
execute/3,
@@ -301,7 +301,7 @@
%% @doc Get the id of the last inserted record.
%%
%% @spec get_last_insert_id(PoolId::atom()) -> term()
-get_last_insert_id(Options) ->
+get_last_insert_id(_Table, Options) ->
case q2(<<"SELECT last_insert_id()">>, Options) of
{data, Result} ->
[[Val]] = mysql:get_result_rows(Result),
Index: /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/
erlydb_psql.erl
===================================================================
--- /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/erlydb_psql.erl
(revision 0)
+++ /home/rsaccon/eclipse-workspace/erlyweb/src/erlydb/erlydb_psql.erl
(revision 0)
@@ -0,0 +1,287 @@
+%% @author Roberto Saccon (rsa...@gmail.com)
+%% @copyright Roberto Saccon 2007
+%%
+%% @doc This module implements the Postgresql driver for ErlyDB.
+%%
+%% Based on code initially developed by Brian Olson, see
+%% (http://groups.google.com/group/erlyweb/browse_frm/thread/
e1585240f790c87c)
+%%
+%% This is an internal ErlyDB module that you normally shouldn't have
to
+%% use directly. Configuration options are currently set via OTP
+%% Environment variables in psql.app
+%%
+
+%% For license information see LICENSE.txt
+
+-module(erlydb_psql).
+
+-author("Roberto Saccon (rsa...@gmail.com)").
+
+-export([start/0,
+ stop/0,
+ get_metadata/1,
+ get_metadata/2,
+ q/1,
+ q/2,
+ transaction/2,
+ select/2,
+ select_as/3,
+ update/2,
+ get_last_insert_id/2]).
+
+%% Useful for debugging
+
+-define(L(Msg), io:format("~p:~b ~p ~n", [?MODULE, ?LINE, Msg])).
+-define(S(Obj), io:format("LOG ~w ~s\n", [?LINE, Obj])).
+
+
+%% @doc Starts the psql and sql applications up if they are not
+%% already started. Any errors that are returned are ignored.
+
+%% @todo catch when a connection is not possible:
+%% for example, it returns {connection_failed, econnrefused}
+
+start() ->
+ application:load(sql),
+ application:load(psql),
+ application:start(sql),
+ application:start(psql).
+
+
+%% @doc Stops the psql and sql applications.
+
+stop() ->
+ application:stop(sql),
+ application:stop(psql).
+
+
+table_names_sql(SchemaName) ->
+ "select tablename from pg_tables where schemaname = '" ++ SchemaName
++ "'".
+
+column_attributes_sql(TableName) ->
+ "SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc,
a.attnotnull" ++
+ " FROM pg_attribute a LEFT JOIN pg_attrdef d" ++
+ " ON a.attrelid = d.adrelid AND a.attnum = d.adnum" ++
+ " WHERE a.attrelid = '" ++ TableName ++ "'::regclass" ++
+ " AND a.attnum > 0 AND NOT a.attisdropped" ++
+ " ORDER BY a.attnum".
+
+constraints_sql(TableName, SchemaName) ->
+ "SELECT column_name, constraint_name FROM " ++
+ "information_schema.constraint_column_usage where " ++
+ "table_name = '" ++ TableName ++ "' AND table_schema = '" ++
SchemaName ++ "'".
+
+
+%% @doc Get the table names and fields for the database.
+%%
+%% @spec get_metadata(StartOptions::proplist()) -> gb_trees()
+
+get_metadata(Options) ->
+ get_metadata(get_pool_id(Options), "public").
+
+get_metadata(PoolName, SchemaName) ->
+ TableNames = sql:q(table_names_sql(SchemaName), PoolName),
+ ConstraintInfo = lists:flatten(
+ [sql:q(constraints_sql(element(1, Name), SchemaName), PoolName) ||
+ Name <- TableNames]),
+ case catch lists:foldl(
+ fun(Table, TablesTree) ->
+ get_metadata(Table, ConstraintInfo, TablesTree, PoolName)
+ end, gb_trees:empty(), TableNames) of
+ {error, _} = Err -> Err;
+ Tree -> {ok, Tree}
+ end.
+
+get_metadata(Table, ConstraintInfo, TablesTree, PoolName) ->
+ Columns = sql:q(column_attributes_sql(element(1, Table)), PoolName),
+ Fields = [new_field(Column, element(1, Table), ConstraintInfo) ||
Column <- Columns],
+ TableName = list_to_atom(element(1, Table)),
+ gb_trees:enter(TableName, lists:reverse(Fields), TablesTree).
+
+new_field(FieldInfo, TableName, ConstraintInfo) ->
+ Name = element(1, FieldInfo),
+ Type = parse_type(element(2, FieldInfo)),
+ {Default, Extra} = parse_default(element(3, FieldInfo)),
+ NotNull = case element(4, FieldInfo) of
+ true -> false;
+ false -> true
+ end,
+ Keys = lists:map(
+ fun(Elem) ->
+ CName = TableName ++ "_pkey",
+ case element(2, Elem) of
+ CName -> element(1, Elem);
+ _ -> none
+ end
+ end,
+ ConstraintInfo),
+ Key = case lists:member(Name, Keys) of
+ true ->
+ primary;
+ _ ->
+ undefined
+ end,
+ erlydb_field:new(list_to_atom(Name), Type, NotNull, Key, Default,
Extra).
+
+
+parse_type(TypeStr) ->
+ case string:chr(TypeStr, 40) of %% 40 == '('
+ 0 ->
+ {list_to_atom(TypeStr), undefined};
+ Idx ->
+ {TypeStr1, [_| Vals]} = lists:split(Idx - 1, TypeStr),
+ {ok, [Len], _} = io_lib:fread("~d", Vals),
+ {list_to_atom(TypeStr1), Len}
+ end.
+
+
+parse_default([]) ->
+ {undefined, undefined};
+
+parse_default(DefaultStr) ->
+ case string:str(DefaultStr, "nextval") of
+ 0 ->
+ Default = hd(string:tokens(DefaultStr, "::")),
+ {Default, undefined};
+ _ ->
+ {undefined, identity}
+ end.
+
+%% @doc Execute a statement directly against the PostgreSQL driver.
If
+%% Options contains the value {allow_unsafe_sql, true}, binary and
string
+%% queries as well as ErlSQL queries with binary and/or string
expressions are
+%% accepted. Otherwise the function crashes.
+
+%% The q() functions are a direct copy from the mysql driver.
+
+q(Statement) ->
+ q(Statement, undefined).
+
+q({esql, Statement}, Options) ->
+ case allow_unsafe_statements(Options) of
+ true ->
+ {ok, q2(erlsql:unsafe_sql(Statement), Options)};
+ _ ->
+ case catch erlsql:sql(Statement) of
+ {error, _} = Err ->
+ exit(Err);
+ Sql ->
+ ?L(["Sql: ", Sql]),
+ {ok, q2(Sql, Options)}
+ end
+ end.
+
+q2(Statement, Options) ->
+ sql:q(Statement, get_pool_id(Options)).
+
+
+allow_unsafe_statements(undefined) ->
+ false;
+allow_unsafe_statements(Options) ->
+ proplists:get_value(allow_unsafe_statements, Options).
+
+get_pool_id(undefined) ->
+ erlydb_psql;
+get_pool_id(Options) ->
+ case proplists:get_value(pool_name, Options) of
+ undefined ->
+ erlydb_psql;
+ Other ->
+ Other
+ end.
+
+
+%% @doc Models a transaction. If an error occurs in the function
provided, then
+%% the transaction will rollback. Otherwise it will commit.
+
+transaction(Fun, Options) ->
+ case sql:transaction(get_pool_id(Options)) of
+ ok ->
+ Result = (catch Fun()),
+ case Result of
+ {'EXIT', Reason} ->
+ sql:rollback(get_pool_id(Options)),
+ {aborted, Reason};
+ Val ->
+ case sql:commit(get_pool_id(Options)) of
+ ok ->
+ {atomic, Val};
+ {error, Reason} ->
+ sql:rollback(get_pool_id(Options)),
+ {aborted, Reason}
+ end
+ end;
+ {error, Reason} ->
+ {aborted, Reason}
+ end.
+
+
+%% @doc Execute a raw SELECT statement.
+%%
+%% @spec select(PoolId::atom(), Statement::statement()) ->
+%% {ok, Rows::list()} | {error, Error}
+select(Statement, Options) ->
+ select2(Statement, Options, []).
+
+
+%% @doc Execute a SELECT statements for records belonging to the
given module,
+%% returning all rows with additional data to support
+%% higher-level ErlyDB features.
+%%
+%% @spec select_as(Module::atom(), Statement::statement(),
+%% FixedCols::tuple()) -> {ok, Rows} | {error, Error}
+select_as(Module, Statement, Options) ->
+ ?L(["Statement: ", Statement]),
+ select2(Statement, Options, [Module, false]).
+
+select2(Statement, Options, FixedVals) ->
+ get_select_result(q(Statement, Options), FixedVals).
+
+get_select_result({ok, _Rows}=Result, undefined) ->
+ Result;
+get_select_result({ok, Rows}, FixedVals)->
+ %% ?L(["Rows: ", Rows]),
+ Result = lists:foldl(
+ fun(Fields, Acc) ->
+ Row = FixedVals ++ tuple_to_list(Fields),
+ [list_to_tuple(Row) | Acc]
+ end, [], Rows),
+ {ok, lists:reverse(Result)};
+
+get_select_result(Other, _) ->
+ Other.
+
+
+%% @doc Execute a DELETE or UPDATE statement.
+%%
+%% @spec update(Statement::statement(), Options::options()) ->
+%% {ok, NumAffected} | {error, Err}
+%%
+%% @todo capture the number of records that were affected,
+%% instead of faking it. Requires probably modification
+%% of modify psql/sql driver to receive the result
+%% as a record as with the mysql driver.
+%%
+update(Statement, Options) ->
+ case q(Statement, Options) of
+ {ok, []} ->
+ {ok, 1};
+ {ok, {sql_error, _}=Err} ->
+ exit(Err);
+ Err ->
+ exit(Err)
+ end.
+
+
+%% @doc Get the id of the last inserted record.
+%%
+%% @spec get_last_insert_id(TableName::atom(), Options::proplist()) -
> term()
+get_last_insert_id(Table, Options) ->
+ TableName = atom_to_list(Table),
+ Sql = "SELECT currval('" ++ TableName ++ "_id_seq');",
+ case q2(Sql, Options) of
+ [{N}] ->
+ {ok, N};
+ Err ->
+ exit(Err)
+ end.
\ No newline at end of file
For now, I think we should avoid creating a fork of the Erlang
Consulting driver. The Erlang Consulting people may be more than happy
to incorporate any of our patches and even to create an svn
repository, which in the long run would be the best thing for the
community. (I have communicated about the same matter with the YXA
developers, and if/when YXA will incorporate the changes I made to the
MySQL driver, I will undo the fork). Let's ask them and then only
create a fork as a last resort.
Thanks again for sharing your work!
Cheers,
Yariv
On Mar 4, 11:52 am, "Yariv Sadan" <yari...@gmail.com> wrote:
> Thank you, Roberto, for sharing this patch with us. I will look at it
> closely in the next couple of days as soon as I have more time.
>
> For now, I think we should avoid creating a fork of the Erlang
> Consulting driver. The Erlang Consulting people may be more than happy
> to incorporate any of our patches and even to create an svn
> repository, which in the long run would be the best thing for the
> community. (I have communicated about the same matter with the YXA
> developers, and if/when YXA will incorporate the changes I made to the
> MySQL driver, I will undo the fork). Let's ask them and then only
> create a fork as a last resort.
Huh, thats the opposite of your previous thoughts (well, that's how
things evolve)
I see two problems by not forking the Erlang consulting driver:
1) that driver seems not to be in active development, I got no answers
when trying to contact the authors of the driver, and when posting
about it on erlang mailing list I also got no replies.
2) that driver gives back a result which is differently structured
than the mysql driver, for example VARCHARS are returned as strings
(in mysql driver as binaries) and status or error messages are comming
as unfiltered binaries (and not directly human readable). I think it
would be better to fix that at the driver itself.
But I might be wrong ...
regards
Roberto
I didn't even remember I had a different position previously :)
>
> I see two problems by not forking the Erlang consulting driver:
>
> 1) that driver seems not to be in active development, I got no answers
> when trying to contact the authors of the driver, and when posting
> about it on erlang mailing list I also got no replies.
>
> 2) that driver gives back a result which is differently structured
> than the mysql driver, for example VARCHARS are returned as strings
> (in mysql driver as binaries) and status or error messages are comming
> as unfiltered binaries (and not directly human readable). I think it
> would be better to fix that at the driver itself.
>
> But I might be wrong ...
>
If you got no replies from the authors after trying to contact them,
and the existing driver is no longer developer and it isn't compatible
with ErlyDB, then I agree it's best to do the fork, at least for the
development phase. Once the fork is in good shape, and if the original
developers want to incorporate our code, then we can work with them to
merge the chages.
Thanks,
Yariv
Roberto
On Mar 22, 3:27 pm, "Can Barışcan" <canbar...@gmail.com> wrote:
> Hi everyone,
> I am not proficient with database drivers but I'd like to ask if somebody is
> working on a postgres driver? Postgres is a must in many cases , mysql is
> in the treat of oracle innodb licences. This is the buzz I heard
> researching on the web. Besides postgres does better with multiple cpus and
> concurrency ... Don't you also think this driver is crucial? I am working
> on a project and I'm waiting for this actually... Should I start with mysql
> and migrate later when the driver comes around? Sorry for being directive.
> Can Barixcan
>