-
Notifications
You must be signed in to change notification settings - Fork 3.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NUMERIC data type not decoding properly in C# (NPGSQL) #56907
Comments
Hello, I am Blathers. I am here to help you get the issue triaged. Hoot - a bug! Though bugs are the bane of my existence, rest assured the wretched thing will get the best of care here. I have CC'd a few people who may be able to assist you:
If we have not gotten back to your issue within a few business days, you can try the following:
🦉 Hoot! I am a Blathers, a bot for CockroachDB. My owner is otan. |
Note also that everything works fine if the numeric type is created with precision and scale: However, even as of CockroachDB 20.2 with the experimental features enabled it still does not support alter table alter column to change from NUMERIC to NUMERIC(30,10), which makes it complicated to change the field type to work around the issue. It requires an add column, set equal to existing column, drop / recreate column, set value back, etc. |
Thanks for filing this! @roji: Whenever you have a moment, would you be able to point me to the code in Npgsql that decodes NUMERIC? I think seeing that code will help me determine how to address this in CockroachDB. |
Thank you for looking into this! |
@rafiss, in case of type handlers ping me since deal with them most of mine time. Will take a look, but it's weird since 10000 is covered by our tests for example. |
@rafiss, as I see the handler correctly writes and reads values to the buffer. Methods for which you are looking are Here are results of mine investigation including a minimal repro: [TestCase(false)]
[TestCase(true)]
public async static Task Cockroach(bool cockroach)
{
const string Cockroach = "Host=localhost;Username=root;Password=root;Database=postgres;Port=26257";
using var conn = new NpgsqlConnection(cockroach ? Cockroach : TestUtil.ConnectionString);
await conn.OpenAsync();
var expected = 10000M;
using var cmd = new NpgsqlCommand($"SELECT {expected}::numeric, @d", conn)
{
Parameters = { new NpgsqlParameter("d", expected) }
};
using var reader = await cmd.ExecuteReaderAsync();
await reader.ReadAsync();
var wired = reader.GetFieldValue<decimal>(0);
var actual = reader.GetFieldValue<decimal>(1);
Assert.AreEqual(expected, actual);
}
As you can see, PostgreSQL returns both values passed to it correctly. Cockroach works well only when the value created on its side, but for the forwarded one it breaks two bytes which gives an incorrect result. |
Thanks! I've reproduced this in our own testing with the following. (It uses our pgwire protocol DSL)
It gets the following differences in the results:
There is one big difference -- the results in my repro show binary data of: So we should get to the bottom of this... But |
I've opened a draft PR #57049 with my testing so far. @YohDeadfall as a next step, could you confirm how Npgsql handles the value And would you be able to confirm that you are getting back the value |
@rafiss, you're correct. Should be
Sending that value to PostgreSQL server causes #define NUMERIC_DSCALE_MASK 0x3FFF if ((value.dscale & NUMERIC_DSCALE_MASK) != value.dscale)
ereport(ERROR,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("invalid scale in external \"numeric\" value"))); As I can see PostgreSQL doesn't allow negative scales. Is it Cockroach specific? |
I think we may be missing a step in our decimal->binary encoding. Postgres does a truncation step that I don't think we implement the same way: https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/numeric.c#L10734 I tried to reproduce the original bug with the Go @YohDeadfall I'm curious if you can reproduce this issue by using the value One last difference I'll note here which could be related. In PG, the CLI shows:
While CockroachDB shows
|
Two things that will help short term, if you were willing to fix this NPGSQL side and have this work for both PG/CRDB. Also put down long term fixes for us:
|
PostgreSQL's numeric has no concept of scientific notation internally, except for when parsing. This means The value So the value 6000500000000.0000000, as an example, shall be encoded like this: If I understand correctly, the only thing you need to make CockroachDB compliant with PostgreSQL clients, when sending values to the client, is to replace negative dscale values with 0. In PostgreSQL, when converting a numeric value to text format, scientific notation is not used. |
Thank you for your insight @Emill! It seems that your analysis is correct, so I've made the change with tests in #64022 @igremmerlb I know it's been a while, but is your project blocked on this issue? Currently, my change is only scheduled to be in v21.2, releasing in late 2021. If this is a blocker, I'll see if we can backport this fix to earlier releases. |
Maybe a bit late but if you are tired reading PostgreSQL C code to understand the binary (and textual) encoding I've written a document here https://www.npgsql.org/doc/dev/type-representations.html that documents most (all?) datatypes. |
@rafiss - thank you for the fix. We appreciate it. @YohDeadfall fixed the issue on the NPGSQL (C# driver) side, so our project is not blocked at this point. If it's easy to get it into v21.1 that would be great, but if not then no worries. Thanks again to everyone involved. |
Describe the problem
Please describe the issue you observed, and any steps we can take to reproduce it:
We have a C# (Net Core) project connecting to a CockroachDB database. Entity Framework (C# ORM) defaults to using DECIMAL/NUMERIC type in CockroachDB for decimal fields in C# (without precision or scale). Reading numbers causes issues when the number is of the form X0000 (e.g. 10000, 20000, 30000). PostgreSQL is interpreted correctly. CockroachDBs values are also interpreted correctly using DBeaver, which uses the Java driver. The issue may be limited to NPGSQL / C#.
To Reproduce
What did you do? Describe in your own words.
Run the following C# code in Net Core (tested with 2.1 and 3.1):
Command line to create project: 'dotnet new console -o npgtest'
namespace npgtest
{
class Program
{
async static Task Main(string[] args)
{
Console.WriteLine("Hello World!");
}
The code returns the following output:
1000, 1000
2000, 2000
3000, 3000
4000, 4000
5000, 5000
6000, 6000
7000, 7000
8000, 8000
9000, 9000
10000, 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
11000, 11000
12000, 12000
13000, 13000
14000, 14000
15000, 15000
16000, 16000
17000, 17000
18000, 18000
19000, 19000
20000, 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002
21000, 21000
22000, 22000
23000, 23000
24000, 24000
25000, 25000
26000, 26000
27000, 27000
28000, 28000
29000, 29000
30000, 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003
31000, 31000
32000, 32000
33000, 33000
34000, 34000
35000, 35000
36000, 36000
37000, 37000
38000, 38000
39000, 39000
40000, 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004
41000, 41000
42000, 42000
43000, 43000
44000, 44000
45000, 45000
46000, 46000
47000, 47000
48000, 48000
49000, 49000
50000, 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005
51000, 51000
52000, 52000
53000, 53000
54000, 54000
55000, 55000
56000, 56000
57000, 57000
58000, 58000
59000, 59000
60000, 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006
61000, 61000
62000, 62000
63000, 63000
64000, 64000
65000, 65000
66000, 66000
67000, 67000
68000, 68000
69000, 69000
70000, 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007
71000, 71000
72000, 72000
73000, 73000
74000, 74000
75000, 75000
76000, 76000
77000, 77000
78000, 78000
79000, 79000
80000, 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008
81000, 81000
82000, 82000
83000, 83000
84000, 84000
85000, 85000
86000, 86000
87000, 87000
88000, 88000
89000, 89000
90000, 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009
91000, 91000
92000, 92000
93000, 93000
94000, 94000
95000, 95000
96000, 96000
97000, 97000
98000, 98000
99000, 99000
If possible, provide steps to reproduce the behavior:
Expected behavior
The first number (integer field) should be the same as the second number (numeric type).
Environment:
cockroach sql
, JDBC, ...] - C# NPGSQL Net Core 3.1 (and 2.1)Additional context
What was the impact?
Corrupted financial data
Add any other context about the problem here.
The text was updated successfully, but these errors were encountered: