1 // Npgsql.NpgsqlCopySerializer.cs
4 // Kalle Hallivuori <kato@iki.fi>
6 // Copyright (C) 2007 The Npgsql Development Team
7 // npgsql-general@gborg.postgresql.org
8 // http://gborg.postgresql.org/project/npgsql/projdisplay.php
10 // Permission to use, copy, modify, and distribute this software and its
11 // documentation for any purpose, without fee, and without a written
12 // agreement is hereby granted, provided that the above copyright notice
13 // and this paragraph and the following two paragraphs appear in all copies.
15 // IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
16 // FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
17 // INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
18 // DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
19 // THE POSSIBILITY OF SUCH DAMAGE.
21 // THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
22 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
23 // AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
24 // ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
25 // TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
35 /// Writes given objects into a stream for PostgreSQL COPY in default copy format (not CSV or BINARY).
37 public class NpgsqlCopySerializer
39 private static readonly Encoding ENCODING_UTF8
= Encoding
.UTF8
;
41 public static String DEFAULT_DELIMITER
= "\t",
42 DEFAULT_SEPARATOR
= "\n",
44 DEFAULT_ESCAPE
= "\\",
47 public static int DEFAULT_BUFFER_SIZE
= 8192;
49 private readonly NpgsqlConnector _context
;
50 private Stream _toStream
;
52 private String _delimiter
= DEFAULT_DELIMITER
,
53 _escape
= DEFAULT_ESCAPE
,
54 _separator
= DEFAULT_SEPARATOR
,
57 private byte[] _delimiterBytes
= null, _escapeBytes
= null, _separatorBytes
= null, _nullBytes
= null;
58 private byte[][] _escapeSequenceBytes
= null;
59 private String
[] _stringsToEscape
= null;
61 private byte[] _sendBuffer
= null;
62 private int _sendBufferAt
= 0, _lastFieldEndAt
= 0, _lastRowEndAt
= 0, _atField
= 0;
64 public NpgsqlCopySerializer(NpgsqlConnection conn
)
66 _context
= conn
.Connector
;
71 get { return _toStream != null && _context.Mediator.CopyStream == _toStream && _context.CurrentState is NpgsqlCopyInState; }
74 public Stream ToStream
78 if (_toStream
== null)
80 _toStream
= _context
.Mediator
.CopyStream
;
88 throw new NpgsqlException("Do not change stream of an active " + this);
94 public String Delimiter
96 get { return _delimiter; }
101 throw new NpgsqlException("Do not change delimiter of an active " + this);
103 _delimiter
= value ?? DEFAULT_DELIMITER
;
104 _delimiterBytes
= null;
105 _stringsToEscape
= null;
106 _escapeSequenceBytes
= null;
110 private byte[] DelimiterBytes
114 if (_delimiterBytes
== null)
116 _delimiterBytes
= ENCODING_UTF8
.GetBytes(_delimiter
);
118 return _delimiterBytes
;
122 public String Separator
124 get { return _separator; }
129 throw new NpgsqlException("Do not change separator of an active " + this);
131 _separator
= value ?? DEFAULT_SEPARATOR
;
132 _separatorBytes
= null;
133 _stringsToEscape
= null;
134 _escapeSequenceBytes
= null;
138 private byte[] SeparatorBytes
142 if (_separatorBytes
== null)
144 _separatorBytes
= ENCODING_UTF8
.GetBytes(_separator
);
146 return _separatorBytes
;
152 get { return _escape; }
157 throw new NpgsqlException("Do not change escape symbol of an active " + this);
159 _escape
= value ?? DEFAULT_ESCAPE
;
161 _stringsToEscape
= null;
162 _escapeSequenceBytes
= null;
166 private byte[] EscapeBytes
170 if (_escapeBytes
== null)
172 _escapeBytes
= ENCODING_UTF8
.GetBytes(_escape
);
180 get { return _null; }
185 throw new NpgsqlException("Do not change null symbol of an active " + this);
187 _null
= value ?? DEFAULT_NULL
;
189 _stringsToEscape
= null;
190 _escapeSequenceBytes
= null;
194 private byte[] NullBytes
198 if (_nullBytes
== null)
200 _nullBytes
= ENCODING_UTF8
.GetBytes(_null
);
206 public Int32 BufferSize
208 get { return _sendBuffer != null ? _sendBuffer.Length : DEFAULT_BUFFER_SIZE; }
211 byte[] _newBuffer
= new byte[value];
212 if (_sendBuffer
!= null)
214 for (int i
= 0; i
< _sendBufferAt
; i
++)
216 _newBuffer
[i
] = _sendBuffer
[i
];
219 _sendBuffer
= _newBuffer
;
225 if (_sendBufferAt
> 0)
227 ToStream
.Write(_sendBuffer
, 0, _sendBufferAt
);
235 public void FlushRows()
237 if (_lastRowEndAt
> 0)
239 ToStream
.Write(_sendBuffer
, 0, _lastRowEndAt
);
241 int len
= _sendBufferAt
- _lastRowEndAt
;
242 for (int i
= 0; i
< len
; i
++)
244 _sendBuffer
[i
] = _sendBuffer
[_lastRowEndAt
+ i
];
246 _lastFieldEndAt
-= _lastRowEndAt
;
247 _sendBufferAt
-= _lastRowEndAt
;
252 public void FlushFields()
254 if (_lastFieldEndAt
> 0)
256 ToStream
.Write(_sendBuffer
, 0, _lastFieldEndAt
);
258 int len
= _sendBufferAt
- _lastFieldEndAt
;
259 for (int i
= 0; i
< len
; i
++)
261 _sendBuffer
[i
] = _sendBuffer
[_lastFieldEndAt
+ i
];
263 _lastRowEndAt
-= _lastFieldEndAt
;
264 _sendBufferAt
-= _lastFieldEndAt
;
279 protected int SpaceInBuffer
281 get { return BufferSize - _sendBufferAt; }
284 protected String
[] StringsToEscape
288 if (_stringsToEscape
== null)
290 _stringsToEscape
= new String
[] {Delimiter, Separator, Escape, "\r", "\n"}
;
292 return _stringsToEscape
;
296 protected byte[][] EscapeSequenceBytes
300 if (_escapeSequenceBytes
== null)
302 _escapeSequenceBytes
= new byte[StringsToEscape
.Length
][];
303 for (int i
= 0; i
< StringsToEscape
.Length
; i
++)
305 _escapeSequenceBytes
[i
] = EscapeSequenceFor(StringsToEscape
[i
].ToCharArray(0, 1)[0]);
308 return _escapeSequenceBytes
;
312 private static readonly byte[] esc_t
= new byte[] {(byte) 't'}
;
314 private static readonly byte[] esc_n
= new byte[] {(byte) 'n'}
;
316 private static readonly byte[] esc_r
= new byte[] {(byte) 'r'}
;
318 private static readonly byte[] esc_b
= new byte[] {(byte) 'b'}
;
320 private static readonly byte[] esc_f
= new byte[] {(byte) 'f'}
;
322 private static readonly byte[] esc_v
= new byte[] {(byte) 'v'}
;
324 protected static byte[] EscapeSequenceFor(char c
)
339 : (c
< 32 || c
> 127)
340 ? new byte[] {(byte) ('0' + ((c/64) & 7)), (byte) ('0' + ((c/8) & 7)), (byte) ('0' + (c & 7))}
341 : new byte[] {(byte) c}
;
344 protected void MakeRoomForBytes(int len
)
346 if (_sendBuffer
== null)
348 _sendBuffer
= new byte[BufferSize
];
350 if (len
>= SpaceInBuffer
)
353 if (len
>= SpaceInBuffer
)
356 if (len
>= SpaceInBuffer
)
364 protected void AddBytes(byte[] bytes
)
366 MakeRoomForBytes(bytes
.Length
);
368 for (int i
= 0; i
< bytes
.Length
; i
++)
370 _sendBuffer
[_sendBufferAt
++] = bytes
[i
];
376 if (_context
!= null)
378 while (_atField
< _context
.CurrentState
.CopyFormat
.FieldCount
)
383 if (_context
== null || ! _context
.CurrentState
.CopyFormat
.IsBinary
)
385 AddBytes(SeparatorBytes
);
387 _lastRowEndAt
= _sendBufferAt
;
391 protected void PrefixField()
395 if (_atField
>= _context
.CurrentState
.CopyFormat
.FieldCount
)
397 throw new NpgsqlException("Tried to add too many fields to a copy record with " + _atField
+ " fields");
399 AddBytes(DelimiterBytes
);
403 protected void FieldAdded()
405 _lastFieldEndAt
= _sendBufferAt
;
409 public void AddNull()
416 public void AddString(String fieldValue
)
419 int bufferedUpto
= 0;
420 while (bufferedUpto
< fieldValue
.Length
)
422 int escapeAt
= fieldValue
.Length
;
423 byte[] escapeSequence
= null;
425 // choose closest instance of strings to escape in fieldValue
426 for (int eachEscapeable
= 0; eachEscapeable
< StringsToEscape
.Length
; eachEscapeable
++)
428 int i
= fieldValue
.IndexOf(StringsToEscape
[eachEscapeable
], bufferedUpto
);
429 if (i
> -1 && i
< escapeAt
)
432 escapeSequence
= EscapeSequenceBytes
[eachEscapeable
];
436 // some, possibly all of fieldValue string does not require escaping and can be buffered for output
437 if (escapeAt
> bufferedUpto
)
439 int encodedLength
= ENCODING_UTF8
.GetByteCount(fieldValue
.ToCharArray(bufferedUpto
, escapeAt
));
440 MakeRoomForBytes(encodedLength
);
441 _sendBufferAt
+= ENCODING_UTF8
.GetBytes(fieldValue
, bufferedUpto
, escapeAt
, _sendBuffer
, _sendBufferAt
);
442 bufferedUpto
= escapeAt
;
445 // now buffer the escape sequence for output
446 if (escapeSequence
!= null)
448 AddBytes(EscapeBytes
);
449 AddBytes(escapeSequence
);
456 public void AddInt32(Int32 fieldValue
)
458 AddString(string.Format("{0}", fieldValue
));
461 public void AddInt64(Int64 fieldValue
)
463 AddString(string.Format("{0}", fieldValue
));
466 public void AddNumber(double fieldValue
)
468 AddString(string.Format("{0}", fieldValue
));
471 public void AddBool(bool fieldValue
)
473 AddString(fieldValue
? "TRUE" : "FALSE");
476 public void AddDateTime(DateTime fieldValue
)
478 AddString(string.Format("{0}-{1}-{2} {3}:{4}:{5}.{6}", fieldValue
.Year
, fieldValue
.Month
, fieldValue
.Day
, fieldValue
.Hour
, fieldValue
.Minute
, fieldValue
.Second
, fieldValue
.Millisecond
));