-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathNpgsqlRestParameter.cs
More file actions
182 lines (160 loc) · 7.23 KB
/
Copy pathNpgsqlRestParameter.cs
File metadata and controls
182 lines (160 loc) · 7.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
using System.Text;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Primitives;
using Npgsql;
namespace NpgsqlRest;
public class NpgsqlRestParameter : NpgsqlParameter
{
public int Ordinal { get; private set; }
public string ConvertedName { get; internal set; }
public string ActualName { get; internal set; }
/// <summary>
/// The original PostgreSQL parameter name as defined in the function/source.
/// Never changed by annotations. Used by RoutineSourceParameterFormatter for SQL generation.
/// </summary>
public string OriginalName { get; init; }
/// <summary>
/// Virtual parameters exist for HTTP matching and claim mapping but are NOT bound
/// to the PostgreSQL command. Created by @define_param annotation on SQL file endpoints.
/// </summary>
public bool IsVirtual { get; init; }
public TypeDescriptor TypeDescriptor { get; set; }
/// <summary>
/// For HTTP custom type composite parameters, the field names in order.
/// Used to build the composite text value without depending on CompositeTypeCache.
/// </summary>
public string[]? CompositeFieldNames { get; set; }
/// <summary>
/// For an HTTP Custom Type field expanded out of a composite parameter, the per-field name as it
/// appears in the generated signature / .http file (base parameter name + separator + field name,
/// e.g. "_response_body"). Unlike <see cref="ActualName"/> — which stays the shared composite base
/// (e.g. "_response") so the fields reassemble into the single SQL argument — this is unique per
/// field. Used only as an additional alias when matching annotations such as @body_parameter_name,
/// so the name a user sees in the signature also resolves. Null for non-expanded parameters.
/// </summary>
public string? ExpandedName { get; set; }
public ParamType ParamType { get; set; } = default!;
public StringValues? QueryStringValues { get; set; } = null;
public JsonNode? JsonBodyNode { get; set; } = null;
public NpgsqlRestParameter? HashOf { get; set; } = null;
/// <summary>
/// The original string representation of the parameter value as received from the request
/// (query string or JSON body). Used for cache key generation to ensure consistency.
/// </summary>
public string? OriginalStringValue { get; set; } = null;
public bool IsUploadMetadata { get; set; } = false;
/// <summary>
/// Explicit default value set by annotation (e.g., @param my_param default null).
/// null = no explicit default (use existing HasDefault/PostgreSQL behavior).
/// DBNull.Value = default is SQL NULL.
/// Any other value = the default value as a string to be bound.
/// </summary>
public object? DefaultValue { get; set; } = null;
public bool IsIpAddress { get; set; } = false;
public string? UserClaim { get; set; } = null;
public bool IsUserClaims { get; set; } = false;
public bool IsFromUserClaims => UserClaim is not null || IsUserClaims is true;
public NpgsqlRestParameter(
int ordinal,
string convertedName,
string actualName,
TypeDescriptor typeDescriptor)
{
Ordinal = ordinal;
ConvertedName = convertedName;
OriginalName = actualName;
ActualName = actualName;
TypeDescriptor = typeDescriptor;
NpgsqlDbType = typeDescriptor.ActualDbType;
if (actualName is not null &&
Options.AuthenticationOptions.ParameterNameClaimsMapping.TryGetValue(actualName, out var claimName))
{
UserClaim = claimName;
}
if (actualName is not null &&
string.Equals(Options.AuthenticationOptions.IpAddressParameterName, actualName, StringComparison.OrdinalIgnoreCase))
{
IsIpAddress = true;
}
if (actualName is not null &&
string.Equals(Options.AuthenticationOptions.ClaimsJsonParameterName, actualName, StringComparison.OrdinalIgnoreCase))
{
IsUserClaims = true;
}
if (Options.UploadOptions.UseDefaultUploadMetadataParameter is true)
{
if (string.Equals(Options.UploadOptions.DefaultUploadMetadataParameterName, actualName, StringComparison.OrdinalIgnoreCase))
{
IsUploadMetadata = true;
}
}
}
private const char CacheKeySeparator = '\x1F'; // Unit Separator - non-printable ASCII character
// Null/DBNull marker, delimited by the Unit Separator (the same byte used between params). Avoids
// \x00, which is hostile across backends (Redis keys, HybridCache key validation, log collectors).
// Collision-free: a real value can never contain \x1F, so it can never produce this marker.
private const string CacheKeyNull = "\x1FNULL\x1F";
internal string GetCacheStringValue()
{
if (Value is null || Value == DBNull.Value)
{
return CacheKeyNull;
}
// Prefer original string value from request for consistency
if (OriginalStringValue is not null)
{
return OriginalStringValue;
}
// Fallback for internally-set values (user claims, IP address, etc.)
if (TypeDescriptor.IsArray)
{
// Arrays can be stored as List<object?> (from query string parsing) or object[]
IList<object?>? list = Value as IList<object?>;
if (list is null || list.Count == 0)
{
return "[]";
}
var sb = new StringBuilder();
sb.Append('[');
for (int i = 0; i < list.Count; i++)
{
if (i > 0)
{
sb.Append(CacheKeySeparator);
}
sb.Append(list[i]?.ToString() ?? CacheKeyNull);
}
sb.Append(']');
return sb.ToString();
}
return Value.ToString() ?? CacheKeyNull;
}
internal static char GetCacheKeySeparator() => CacheKeySeparator;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
#pragma warning disable CS8603 // Possible null reference return.
public NpgsqlRestParameter NpgsqlRestParameterMemberwiseClone() => MemberwiseClone() as NpgsqlRestParameter;
private NpgsqlRestParameter() { }
#pragma warning restore CS8603 // Possible null reference return.
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
private static readonly NpgsqlRestParameter TextParam = new()
{
NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Text,
Value = DBNull.Value,
};
public static NpgsqlParameter CreateParamWithType(NpgsqlTypes.NpgsqlDbType type)
{
var result = TextParam.NpgsqlRestParameterMemberwiseClone();
result.NpgsqlDbType = type;
return result;
}
public static NpgsqlParameter CreateTextParam(object? value)
{
var result = TextParam.NpgsqlRestParameterMemberwiseClone();
if (value is null)
{
return result;
}
result.Value = value;
return result;
}
}