@@ -145,6 +145,23 @@ def _not(t, op):
145
145
return sa .case ((arg == 0 , True ), else_ = False )
146
146
147
147
148
+ def _len (x ):
149
+ """The MSSQL LEN function doesn't count trailing spaces.
150
+
151
+ Also, DATALENGTH (the suggested alternative) counts bytes and thus its
152
+ result depends on the string's encoding.
153
+
154
+ https://learn.microsoft.com/en-us/sql/t-sql/functions/len-transact-sql?view=sql-server-ver16#remarks
155
+
156
+ The solution is to add a character to the beginning and end of the string
157
+ that are guaranteed to have one character in length and are not spaces, and
158
+ then subtract 2 from the result of `LEN` of that input.
159
+
160
+ Thanks to @arkanovicz for this glorious hack.
161
+ """
162
+ return sa .func .len ("A" + x + "Z" ) - 2
163
+
164
+
148
165
operation_registry = sqlalchemy_operation_registry .copy ()
149
166
operation_registry .update (sqlalchemy_window_functions_registry )
150
167
@@ -162,7 +179,7 @@ def _not(t, op):
162
179
ops .Capitalize : unary (
163
180
lambda arg : sa .func .concat (
164
181
sa .func .upper (sa .func .substring (arg , 1 , 1 )),
165
- sa .func .lower (sa .func .substring (arg , 2 , sa . func . datalength (arg ) - 1 )),
182
+ sa .func .lower (sa .func .substring (arg , 2 , _len (arg ) - 1 )),
166
183
)
167
184
),
168
185
ops .LStrip : unary (sa .func .ltrim ),
@@ -171,7 +188,7 @@ def _not(t, op):
171
188
ops .Repeat : fixed_arity (sa .func .replicate , 2 ),
172
189
ops .Reverse : unary (sa .func .reverse ),
173
190
ops .StringFind : _string_find ,
174
- ops .StringLength : unary (sa . func . datalength ),
191
+ ops .StringLength : unary (_len ),
175
192
ops .StringReplace : fixed_arity (sa .func .replace , 3 ),
176
193
ops .Strip : unary (sa .func .trim ),
177
194
ops .Uppercase : unary (sa .func .upper ),
0 commit comments