diff --git a/.changeset/old-lamps-hear.md b/.changeset/old-lamps-hear.md new file mode 100644 index 00000000..64fc5c18 --- /dev/null +++ b/.changeset/old-lamps-hear.md @@ -0,0 +1,5 @@ +--- +"@core/sync-service": patch +--- + +fix: correctly parse larger set of Postgres intervals with signs diff --git a/packages/sync-service/lib/pg_interop/interval/postgres_and_sql_parser.ex b/packages/sync-service/lib/pg_interop/interval/postgres_and_sql_parser.ex index 15529fff..8cdc46b0 100644 --- a/packages/sync-service/lib/pg_interop/interval/postgres_and_sql_parser.ex +++ b/packages/sync-service/lib/pg_interop/interval/postgres_and_sql_parser.ex @@ -5,24 +5,30 @@ defmodule PgInterop.Interval.PostgresAndSQLParser do alias PgInterop.Interval @parse_part_regexes [ - unmarked_end: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)(?=\s*$)/, - microsecond: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:us|usecs?|microseconds?)(?=\s|$)/, - millisecond: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:ms|msecs?|milliseconds?)(?=\s|$)/, - second: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:s|secs?|seconds?)(?=\s|$)/, - minute: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:m|mins?|minutes?)(?=\s|$)/, - hour: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:h|hours?)(?=\s|$)/, - day: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:d|days?)(?=\s|$)/, - week: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:w|weeks?)(?=\s|$)/, - month: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:m|mons?|months?)(?=\s|$)/, - year: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:y|years?)(?=\s|$)/, - decade: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:decs?|decades?)(?=\s|$)/, - century: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:c|cent|century|centuries)(?=\s|$)/, - millennium: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)\s*(?:mils|millenniums)(?=\s|$)/, - sql_ym: ~r/(?<=\s|^)(?\d+)-(?\d+)(?=\s|$)/, - sql_dhm: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)?\s+(?\d+):(?\d+)(?=\s|$)/, + unmarked_end: ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)(?=\s*$)/, + microsecond: + ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:us|usecs?|microseconds?)(?=\s|$)/, + millisecond: + ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:ms|msecs?|milliseconds?)(?=\s|$)/, + second: ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:s|secs?|seconds?)(?=\s|$)/, + minute: ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:m|mins?|minutes?)(?=\s|$)/, + hour: ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:h|hours?)(?=\s|$)/, + day: ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:d|days?)(?=\s|$)/, + week: ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:w|weeks?)(?=\s|$)/, + month: ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:m|mons?|months?)(?=\s|$)/, + year: ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:y|years?)(?=\s|$)/, + decade: ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:decs?|decades?)(?=\s|$)/, + century: + ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:c|cent|century|centuries)(?=\s|$)/, + millennium: + ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)\s*(?:mils|millenniums)(?=\s|$)/, + sql_ym: ~r/(?<=\s|^)(?\+|-)?\s*(?\d+)-(?\-?\d+)(?=\s|$)/, + sql_dhm: + ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)?\s+(?\+|-)?\s*(?\d+):(?\d+)(?=\s|$)/, sql_dhms: - ~r/(?<=\s|^)(?\d+(?:\.\d+)?)?\s+(?\d+):(?\d+):(?\d+(?:\.\d+)?)(?=\s|$)/, - sql_dms: ~r/(?<=\s|^)(?\d+(?:\.\d+)?)?\s+(?\d+):(?\d+\.\d+)(?=\s|$)/ + ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)?\s+(?\+|-)?\s*(?\d+):(?\d+):(?\d+(?:\.\d+)?)(?=\s|$)/, + sql_dms: + ~r/(?<=\s|^)(?(?:\+|-)?\s*\d+(?:\.\d+)?)?\s+(?\+|-)?\s*(?\d+):(?\d+\.\d+)(?=\s|$)/ ] @doc """ @@ -37,24 +43,39 @@ defmodule PgInterop.Interval.PostgresAndSQLParser do iex> parse("@ 1-2") {:ok, Interval.parse!("P1Y2M")} + iex> parse("-1-2 +3 -4:05:06") + {:ok, Interval.parse!("P-1Y-2M3DT-4H-5M-6S")} + + iex> parse("-1-2 -5:10.1") + {:ok, Interval.parse!("P-1Y-2MT-5M-10.1S")} + iex> parse("3 4:05:06") {:ok, Interval.parse!("P3DT4H5M6S")} iex> parse("1 year 2 months 3 days 4 hours 5 minutes 6 seconds") {:ok, Interval.parse!("P1Y2M3DT4H5M6S")} - iex> parse("1 year 2-1 3 days 2") + iex> parse("1 year 2-1 3 days +2") {:ok, Interval.parse!("P3Y1M3DT2S")} + iex> parse("1 year 2-1 3 days -2") + {:ok, Interval.parse!("P3Y1M3DT-2S")} + iex> parse("1 year 2-1 3 days 2:2") {:ok, Interval.parse!("P3Y1M3DT2H2M")} iex> parse("1 year 2-1 3 days 2.2") {:ok, Interval.parse!("P3Y1M3DT2.2S")} + iex> parse("-1-2 -5:10.1") + {:ok, Interval.parse!("P-1Y-2MT-5M-10.1S")} + iex> parse("1.3 cent 100-11 10.3") {:ok, Interval.parse!("P230Y11MT10.3S")} + iex> parse("- 1 year -2 mons +3 days - 04:05:06") + {:ok, Interval.parse!("P-1Y-2M3DT-4H-5M-6S")} + iex> parse("0.1 mils 1 cent 1 decade 1 year 1 month 1 week 1 day 1 hour 1 minute 1 second 1000 ms 1000000 us") {:ok, Interval.parse!("P211Y1M8DT1H1M3S")} @@ -92,6 +113,7 @@ defmodule PgInterop.Interval.PostgresAndSQLParser do with :ok <- validate_parts(Map.new(parsed_parts)) do {:ok, parsed_parts + |> Enum.map(&apply_sign/1) |> Keyword.values() |> Enum.reject(&is_nil/1) |> Enum.flat_map(&Enum.map(&1, fn {k, v} -> build_duration(k, parse_float!(v)) end)) @@ -102,10 +124,34 @@ defmodule PgInterop.Interval.PostgresAndSQLParser do end end + defp apply_sign({k, %{"sign" => sign} = v}) when sign == "+" or sign == "", + do: {k, Map.delete(v, "sign")} + + defp apply_sign({k, %{"sign" => "-"} = v}) do + v = + v + |> Map.delete("sign") + |> Map.new(fn + {"day", _} = day -> day + {part, val} -> {part, "-" <> val} + end) + + {k, v} + end + + defp apply_sign(no_sign), do: no_sign + defp parse_float!(""), do: 0 defp parse_float!(v) do - {float, ""} = Float.parse(v) + prepared = + case v do + "+" <> number -> String.trim(number) + "-" <> number -> "-" <> String.trim(number) + number -> String.trim(number) + end + + {float, ""} = Float.parse(prepared) float end