@@ -939,28 +939,30 @@ defmodule Ecto.Adapters.LibSql.MigrationTest do
939939 test "handles unexpected types gracefully (empty map)" do
940940 # This test verifies the catch-all clause for unexpected types.
941941 # Empty maps can come from some migrations or other third-party code.
942+ # As of the defaults update, empty maps are JSON encoded like other maps.
942943 table = % Table { name: :users , prefix: nil }
943944 columns = [ { :add , :metadata , :string , [ default: % { } ] } ]
944945
945946 # Should not raise FunctionClauseError.
946947 [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
947948
948- # Empty map should be treated as no default.
949- assert sql =~ ~r/ "metadata".*TEXT/
950- refute sql =~ ~r / "metadata".*DEFAULT /
949+ # Empty map should be JSON encoded to '{}'
950+ assert sql =~ ~r/ "metadata".*TEXT.*DEFAULT /
951+ assert sql =~ "'{}'"
951952 end
952953
953954 test "handles unexpected types gracefully (list)" do
954955 # Lists are another unexpected type that might appear.
956+ # As of the defaults update, lists are JSON encoded.
955957 table = % Table { name: :users , prefix: nil }
956958 columns = [ { :add , :tags , :string , [ default: [ ] ] } ]
957959
958960 # Should not raise FunctionClauseError.
959961 [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
960962
961- # Empty list should be treated as no default.
962- assert sql =~ ~r/ "tags".*TEXT/
963- refute sql =~ ~r / "tags".* DEFAULT/
963+ # Empty list should be JSON encoded to '[]'
964+ assert sql =~ ~r/ "tags".*TEXT.*DEFAULT /
965+ assert sql =~ " DEFAULT '[]'"
964966 end
965967
966968 test "handles unexpected types gracefully (atom)" do
@@ -975,6 +977,235 @@ defmodule Ecto.Adapters.LibSql.MigrationTest do
975977 assert sql =~ ~r/ "status".*TEXT/
976978 refute sql =~ ~r/ "status".*DEFAULT/
977979 end
980+
981+ test "handles map defaults (JSON encoding)" do
982+ table = % Table { name: :users , prefix: nil }
983+
984+ columns = [
985+ { :add , :preferences , :text , [ default: % { "theme" => "dark" , "notifications" => true } ] }
986+ ]
987+
988+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
989+
990+ # Map should be JSON encoded
991+ assert sql =~ ~r/ "preferences".*TEXT.*DEFAULT/
992+
993+ [ _ , json ] = Regex . run ( ~r/ DEFAULT '([^']*)'/ , sql )
994+
995+ assert Jason . decode! ( json ) == % {
996+ "theme" => "dark" ,
997+ "notifications" => true
998+ }
999+ end
1000+
1001+ test "handles list defaults (JSON encoding)" do
1002+ table = % Table { name: :items , prefix: nil }
1003+
1004+ columns = [
1005+ { :add , :tags , :text , [ default: [ "tag1" , "tag2" , "tag3" ] ] }
1006+ ]
1007+
1008+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1009+
1010+ # List should be JSON encoded
1011+ assert sql =~ ~r/ "tags".*TEXT.*DEFAULT/
1012+
1013+ [ _ , json ] = Regex . run ( ~r/ DEFAULT '([^']*)'/ , sql )
1014+
1015+ assert Jason . decode! ( json ) == [ "tag1" , "tag2" , "tag3" ]
1016+ end
1017+
1018+ test "handles empty list defaults" do
1019+ table = % Table { name: :items , prefix: nil }
1020+ columns = [ { :add , :tags , :text , [ default: [ ] ] } ]
1021+
1022+ # Empty list encodes to "[]" in JSON
1023+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1024+
1025+ # Should have a DEFAULT clause with empty array JSON
1026+ assert sql =~ ~r/ "tags".*TEXT.*DEFAULT/
1027+
1028+ [ _ , json ] = Regex . run ( ~r/ DEFAULT '([^']*)'/ , sql )
1029+
1030+ assert Jason . decode! ( json ) == [ ]
1031+ end
1032+
1033+ test "handles complex nested map defaults" do
1034+ table = % Table { name: :configs , prefix: nil }
1035+
1036+ columns = [
1037+ { :add , :settings , :text ,
1038+ [ default: % { "user" => % { "theme" => "light" } , "privacy" => false } ] }
1039+ ]
1040+
1041+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1042+
1043+ # Nested map should be JSON encoded
1044+ assert sql =~ ~r/ "settings".*TEXT.*DEFAULT/
1045+
1046+ [ _ , json ] = Regex . run ( ~r/ DEFAULT '([^']*)'/ , sql )
1047+
1048+ assert Jason . decode! ( json ) == % {
1049+ "user" => % { "theme" => "light" } ,
1050+ "privacy" => false
1051+ }
1052+ end
1053+
1054+ test "handles map with various JSON types" do
1055+ table = % Table { name: :data , prefix: nil }
1056+
1057+ columns = [
1058+ { :add , :metadata , :text ,
1059+ [ default: % { "string" => "value" , "number" => 42 , "bool" => true , "null" => nil } ] }
1060+ ]
1061+
1062+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1063+
1064+ assert sql =~ ~r/ "metadata".*TEXT.*DEFAULT/
1065+
1066+ # Verify JSON is properly escaped - all keys must be present including null values
1067+ [ _ , json ] = Regex . run ( ~r/ DEFAULT '([^']*)'/ , sql )
1068+
1069+ assert Jason . decode! ( json ) == % {
1070+ "string" => "value" ,
1071+ "number" => 42 ,
1072+ "bool" => true ,
1073+ "null" => nil
1074+ }
1075+ end
1076+
1077+ test "logs warning when map has unencodable value (PID)" do
1078+ # Maps containing PIDs or functions cannot be JSON encoded
1079+ table = % Table { name: :data , prefix: nil }
1080+ pid = spawn ( fn -> :ok end )
1081+
1082+ columns = [
1083+ { :add , :metadata , :text , [ default: % { "pid" => pid } ] }
1084+ ]
1085+
1086+ # Capture logs to verify warning is logged
1087+ log_output =
1088+ ExUnit.CaptureLog . capture_log ( fn ->
1089+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1090+
1091+ # When encoding fails, no DEFAULT clause should be generated
1092+ assert sql =~ ~r/ "metadata".*TEXT/
1093+ refute sql =~ ~r/ "metadata".*DEFAULT/
1094+ end )
1095+
1096+ assert log_output =~ "Failed to JSON encode map default value in migration"
1097+ end
1098+
1099+ test "logs warning when list has unencodable value (function)" do
1100+ # Lists containing functions cannot be JSON encoded
1101+ table = % Table { name: :data , prefix: nil }
1102+ func = fn -> :ok end
1103+
1104+ columns = [
1105+ { :add , :callbacks , :text , [ default: [ func , "other" ] ] }
1106+ ]
1107+
1108+ # Capture logs to verify warning is logged
1109+ log_output =
1110+ ExUnit.CaptureLog . capture_log ( fn ->
1111+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1112+
1113+ # When encoding fails, no DEFAULT clause should be generated
1114+ assert sql =~ ~r/ "callbacks".*TEXT/
1115+ refute sql =~ ~r/ "callbacks".*DEFAULT/
1116+ end )
1117+
1118+ assert log_output =~ "Failed to JSON encode list default value in migration"
1119+ end
1120+
1121+ test "handles :null atom defaults (same as nil)" do
1122+ table = % Table { name: :users , prefix: nil }
1123+ columns = [ { :add , :bio , :text , [ default: :null ] } ]
1124+
1125+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1126+
1127+ # :null should result in no DEFAULT clause (same as nil)
1128+ refute sql =~ "DEFAULT"
1129+ end
1130+
1131+ test "handles Decimal defaults" do
1132+ table = % Table { name: :products , prefix: nil }
1133+ columns = [ { :add , :price , :decimal , [ default: Decimal . new ( "19.99" ) ] } ]
1134+
1135+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1136+
1137+ # Decimal should be converted to string representation
1138+ assert sql =~ ~r/ "price".*DECIMAL.*DEFAULT/
1139+ assert sql =~ "'19.99'"
1140+ end
1141+
1142+ test "handles DateTime defaults" do
1143+ table = % Table { name: :events , prefix: nil }
1144+ { :ok , dt , _ } = DateTime . from_iso8601 ( "2026-01-16T14:30:00Z" )
1145+ columns = [ { :add , :created_at , :utc_datetime , [ default: dt ] } ]
1146+
1147+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1148+
1149+ # DateTime should be converted to ISO8601 string
1150+ assert sql =~ ~r/ "created_at".*DATETIME.*DEFAULT/
1151+ assert sql =~ "2026-01-16T14:30:00Z"
1152+ end
1153+
1154+ test "handles NaiveDateTime defaults" do
1155+ table = % Table { name: :logs , prefix: nil }
1156+ dt = ~N[ 2026-01-16 14:30:00.000000]
1157+ columns = [ { :add , :recorded_at , :naive_datetime , [ default: dt ] } ]
1158+
1159+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1160+
1161+ # NaiveDateTime should be converted to ISO8601 string
1162+ assert sql =~ ~r/ "recorded_at".*DATETIME.*DEFAULT/
1163+ assert sql =~ "2026-01-16T14:30:00"
1164+ end
1165+
1166+ test "handles Date defaults" do
1167+ table = % Table { name: :schedules , prefix: nil }
1168+ date = ~D[ 2026-01-16]
1169+ columns = [ { :add , :event_date , :date , [ default: date ] } ]
1170+
1171+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1172+
1173+ # Date should be converted to ISO8601 string
1174+ assert sql =~ ~r/ "event_date".*DATE.*DEFAULT/
1175+ assert sql =~ "'2026-01-16'"
1176+ end
1177+
1178+ test "handles Time defaults" do
1179+ table = % Table { name: :schedules , prefix: nil }
1180+ time = ~T[ 14:30:45.123456]
1181+ columns = [ { :add , :event_time , :time , [ default: time ] } ]
1182+
1183+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1184+
1185+ # Time should be converted to ISO8601 string
1186+ assert sql =~ ~r/ "event_time".*TIME.*DEFAULT/
1187+ assert sql =~ "14:30:45.123456"
1188+ end
1189+
1190+ test "handles Decimal with many decimal places" do
1191+ table = % Table { name: :data , prefix: nil }
1192+ columns = [ { :add , :value , :decimal , [ default: Decimal . new ( "123.456789012345" ) ] } ]
1193+
1194+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1195+
1196+ assert sql =~ ~r/ "value".*DECIMAL.*DEFAULT/
1197+ assert sql =~ "'123.456789012345'"
1198+ end
1199+
1200+ test "handles negative Decimal defaults" do
1201+ table = % Table { name: :balances , prefix: nil }
1202+ columns = [ { :add , :amount , :decimal , [ default: Decimal . new ( "-42.50" ) ] } ]
1203+
1204+ [ sql ] = Connection . execute_ddl ( { :create , table , columns } )
1205+
1206+ assert sql =~ ~r/ "amount".*DECIMAL.*DEFAULT/
1207+ assert sql =~ "'-42.50'"
1208+ end
9781209 end
9791210
9801211 describe "CHECK constraints" do
0 commit comments