MySQL database connects on haxe 4.3.7 but fails on haxe 5/nightly
haxelib git logging https://github.com/core-haxe/logging
haxelib git db-core https://github.com/core-haxe/db-core
haxelib git db-mysql https://github.com/core-haxe/db-mysql
haxelib git mysql https://github.com/core-haxe/mysql
haxelib git promises https://github.com/core-haxe/promises
haxelib git libmysqlclient https://github.com/core-haxe/libmysqlclient
-D analyzer-optimize
-main Main
--cpp bin/cpp
--library db-core
--library db-mysql
--debug
import haxe.Timer;
import db.DatabaseFactory;
class Main {
static function main() {
trace('here');
var connected:Bool = false;
var timer = new Timer(1000);
timer.run = () -> {
trace('connected? $connected');
}
var db = DatabaseFactory.instance.createDatabase(DatabaseFactory.MYSQL, {
database: 'test',
host: "127.0.0.1",
user: "root",
pass: "123456"
});
db.connect().then(function(state) {
trace('here');
if (state.data) {
connected = true;
trace('Database connected');
} else {
trace('Database not connected');
}
}, (err) -> trace(err));
}
}
Occurs on both Windows and Linux, works on haxe 4.3.7 and on nightlies it compiles fine, runs but fails to connect (with no errors)
I set up a mariadb server with a new user using the instructions on the arch wiki: https://wiki.archlinux.org/title/MariaDB
I managed to bisect the issue to: 0b2b0daacd9125317fa355c85df69c26acbeb83d. Here is the diff in generated c++ code:
diff --git a/bin/cpp/src/db/mysql/MySqlTable.cpp b/bin/cpp/src/db/mysql/MySqlTable.cpp
index 2191191..bcb5b4e 100644
--- a/bin/cpp/src/db/mysql/MySqlTable.cpp
+++ b/bin/cpp/src/db/mysql/MySqlTable.cpp
@@ -937,8 +937,8 @@ HXDLIN( 214) _hx_tmp->log( ::Dynamic(::hx::Anon_obj::Create(6)
}
HXLINE( 215) ::Array< ::Dynamic> promises = ::Array_obj< ::Dynamic>::__new(0);
HXLINE( 216) {
-HXLINE( 216) ::db::_RecordSet::RecordSetIterator record = records->iterator();
-HXDLIN( 216) while(record->hasNext()){
+HXLINE( 216) ::Dynamic record = records->iterator();
+HXDLIN( 216) while(( (bool)(record->__Field(HX_("hasNext",6d,a5,46,18),::hx::paccDynamic)()) )){
HX_BEGIN_LOCAL_FUNC_S2(::hx::LocalFunc,_hx_Closure_0, ::db::mysql::MySqlTable,_g, ::db::Record,record2) HXARGC(0)
::Dynamic _hx_run(){
HX_GC_STACKFRAME(&_hx_pos_a490e2ce06945538_217_addAll)
@@ -946,7 +946,7 @@ HXLINE( 217) return _g->add(record2);
}
HX_END_LOCAL_FUNC0(return)
-HXLINE( 216) ::db::Record record1 = record->next();
+HXLINE( 216) ::db::Record record1 = ( ( ::db::Record)(record->__Field(HX_("next",f3,84,02,49),::hx::paccDynamic)()) );
HXLINE( 217) ::db::mysql::MySqlTable _g = _gthis;
HXDLIN( 217) ::db::Record record2 = record1;
HXDLIN( 217) promises->push( ::Dynamic(new _hx_Closure_0(_g,record2)));
diff --git a/bin/cpp/src/mysql/impl/cpp/DatabaseConnection.cpp b/bin/cpp/src/mysql/impl/cpp/DatabaseConnection.cpp
index 00cbd9b..29585dd 100644
--- a/bin/cpp/src/mysql/impl/cpp/DatabaseConnection.cpp
+++ b/bin/cpp/src/mysql/impl/cpp/DatabaseConnection.cpp
@@ -225,9 +225,9 @@ HXLINE( 73) return;
}
HXLINE( 76) ::Dynamic first = null();
HXLINE( 77) {
-HXLINE( 77) ::mysql::_MySqlClientResult::MySqlResultDataIterator record1 = rs->iterator();
-HXDLIN( 77) while(record1->hasNext()){
-HXLINE( 77) ::Dynamic record2 = record1->next();
+HXLINE( 77) ::Dynamic record1 = rs->iterator();
+HXDLIN( 77) while(( (bool)(record1->__Field(HX_("hasNext",6d,a5,46,18),::hx::paccDynamic)()) )){
+HXLINE( 77) ::Dynamic record2 = record1->__Field(HX_("next",f3,84,02,49),::hx::paccDynamic)();
HXLINE( 78) if (::hx::IsNull( first )) {
HXLINE( 79) first = record2;
HXLINE( 80) goto _hx_goto_6;
@@ -292,9 +292,9 @@ HXLINE( 103) sql1[0] = _gthis->prepareSQL(sql1->__get(0),param);
HXLINE( 104) ::mysql::MySqlClientResult rs = _gthis->_nativeConnection->query(sql1->__get(0));
HXLINE( 105) ::cpp::VirtualArray records = ::cpp::VirtualArray_obj::__new(0);
HXLINE( 106) {
-HXLINE( 106) ::mysql::_MySqlClientResult::MySqlResultDataIterator record = rs->iterator();
-HXDLIN( 106) while(record->hasNext()){
-HXLINE( 106) ::Dynamic record1 = record->next();
+HXLINE( 106) ::Dynamic record = rs->iterator();
+HXDLIN( 106) while(( (bool)(record->__Field(HX_("hasNext",6d,a5,46,18),::hx::paccDynamic)()) )){
+HXLINE( 106) ::Dynamic record1 = record->__Field(HX_("next",f3,84,02,49),::hx::paccDynamic)();
HXLINE( 107) records->push(record1);
}
}
@@ -348,9 +348,9 @@ HXLINE( 121) sql1[0] = _gthis->prepareSQL(sql1->__get(0),param);
HXLINE( 122) ::mysql::MySqlClientResult rs = _gthis->_nativeConnection->query(sql1->__get(0));
HXLINE( 123) ::cpp::VirtualArray records = ::cpp::VirtualArray_obj::__new(0);
HXLINE( 124) {
-HXLINE( 124) ::mysql::_MySqlClientResult::MySqlResultDataIterator record = rs->iterator();
-HXDLIN( 124) while(record->hasNext()){
-HXLINE( 124) ::Dynamic record1 = record->next();
+HXLINE( 124) ::Dynamic record = rs->iterator();
+HXDLIN( 124) while(( (bool)(record->__Field(HX_("hasNext",6d,a5,46,18),::hx::paccDynamic)()) )){
+HXLINE( 124) ::Dynamic record1 = record->__Field(HX_("next",f3,84,02,49),::hx::paccDynamic)();
HXLINE( 125) records->push(record1);
}
}
I think this is why the dynamic method calls don't work:
https://github.com/core-haxe/libmysqlclient/blob/86839f69b491d996fc1cc4b24eae22e6034948b1/src/mysql/MySqlClientResult.hx#L14
Here's a more minimal sample to reproduce the issue:
// Main.hx
@:unreflective
class MyIterator {
var count = 0;
final max:Int;
public function new(max:Int) {
this.max = max;
}
public function hasNext():Bool {
return count < max;
}
public function next() {
return count++;
}
}
function main() {
for (i in new MyIterator(10)) {
trace(i);
}
}
# build.hxml
-D analyzer-optimize
-main Main
--cpp bin/cpp
# clean up cpp output
-D no-debug
diff --git a/bin/cpp/src/_Main/Main_Fields_.cpp b/bin/cpp/src/_Main/Main_Fields_.cpp
index aface0a..8a569a4 100644
--- a/bin/cpp/src/_Main/Main_Fields_.cpp
+++ b/bin/cpp/src/_Main/Main_Fields_.cpp
@@ -32,9 +32,9 @@ bool Main_Fields__obj::_hx_isInstanceOf(int inClassId) {
void Main_Fields__obj::main(){
HX_JUST_GC_STACKFRAME
- ::MyIterator i = ::MyIterator_obj::__alloc( HX_CTX ,10);
- while(i->hasNext()){
- int i1 = i->next();
+ ::Dynamic i = ::MyIterator_obj::__alloc( HX_CTX ,10);
+ while(( (bool)(i->__Field(HX_("hasNext",6d,a5,46,18),::hx::paccDynamic)()) )){
+ int i1 = ( (int)(i->__Field(HX_("next",f3,84,02,49),::hx::paccDynamic)()) );
::haxe::Log_obj::trace(i1,::hx::SourceInfo(HX_("src/Main.hx",9a,7a,30,a1),21,HX_("_Main.Main_Fields_",76,cc,48,1a),HX_("main",39,38,56,48)));
}
}
I was also concerned about why the promise isn't being rejected properly and no error is reported. It seems to be due to an assumption made by db-mysql or the promises lib that only MySqlError can be thrown.
https://github.com/core-haxe/db-mysql/blob/3a38aa0193a2e4e863a7acfc2254ab21fd6ec607/src/db/mysql/MySqlDatabase.hx#L191
The null function exception was being caught by this handler, but since it is not a MySqlError, when trying to get the .message field there is another invalid access exception, which means the promise is never rejected: https://github.com/core-haxe/db-mysql/blob/3a38aa0193a2e4e863a7acfc2254ab21fd6ec607/src/db/mysql/Utils.hx#L16
If you enable logging however, then you do a message saying that there was an error connecting.
@kLabz this line specifically is what broke it: https://github.com/HaxeFoundation/haxe/commit/0b2b0daacd9125317fa355c85df69c26acbeb83d#diff-fae87f6bc39fbf2aa94659224d806082a9c651c9e8921bb2c1b81b1875cd3370R417
ctx.t.titerator pt is a dynamic Iterator object, which means dynamic field resolution is used for the next and hasNext fields. This breaks iterators that aren't reflective and is not ideal anyway for static targets where direct field access is possible.
Do you think it would be ok to change it back to use e1.etype? This solves this crash.