disable broken tests on net_4_0
[mcs.git] / class / Npgsql / Npgsql / NpgsqlCopySerializer.cs
blob17e711f8e14e76ec1abdd05716fe5933dae40db0
1 // Npgsql.NpgsqlCopySerializer.cs
2 //
3 // Author:
4 // Kalle Hallivuori <kato@iki.fi>
5 //
6 // Copyright (C) 2007 The Npgsql Development Team
7 // npgsql-general@gborg.postgresql.org
8 // http://gborg.postgresql.org/project/npgsql/projdisplay.php
9 //
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.
14 //
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.
20 //
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.
28 using System;
29 using System.IO;
30 using System.Text;
32 namespace Npgsql
34 /// <summary>
35 /// Writes given objects into a stream for PostgreSQL COPY in default copy format (not CSV or BINARY).
36 /// </summary>
37 public class NpgsqlCopySerializer
39 private static readonly Encoding ENCODING_UTF8 = Encoding.UTF8;
41 public static String DEFAULT_DELIMITER = "\t",
42 DEFAULT_SEPARATOR = "\n",
43 DEFAULT_NULL = "\\N",
44 DEFAULT_ESCAPE = "\\",
45 DEFAULT_QUOTE = "\"";
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,
55 _null = DEFAULT_NULL;
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;
69 public bool IsActive
71 get { return _toStream != null && _context.Mediator.CopyStream == _toStream && _context.CurrentState is NpgsqlCopyInState; }
74 public Stream ToStream
76 get
78 if (_toStream == null)
80 _toStream = _context.Mediator.CopyStream;
82 return _toStream;
84 set
86 if (IsActive)
88 throw new NpgsqlException("Do not change stream of an active " + this);
90 _toStream = value;
94 public String Delimiter
96 get { return _delimiter; }
97 set
99 if (IsActive)
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; }
127 if (IsActive)
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;
150 public String Escape
152 get { return _escape; }
155 if (IsActive)
157 throw new NpgsqlException("Do not change escape symbol of an active " + this);
159 _escape = value ?? DEFAULT_ESCAPE;
160 _escapeBytes = null;
161 _stringsToEscape = null;
162 _escapeSequenceBytes = null;
166 private byte[] EscapeBytes
170 if (_escapeBytes == null)
172 _escapeBytes = ENCODING_UTF8.GetBytes(_escape);
174 return _escapeBytes;
178 public String Null
180 get { return _null; }
183 if (IsActive)
185 throw new NpgsqlException("Do not change null symbol of an active " + this);
187 _null = value ?? DEFAULT_NULL;
188 _nullBytes = null;
189 _stringsToEscape = null;
190 _escapeSequenceBytes = null;
194 private byte[] NullBytes
198 if (_nullBytes == null)
200 _nullBytes = ENCODING_UTF8.GetBytes(_null);
202 return _nullBytes;
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;
223 public void Flush()
225 if (_sendBufferAt > 0)
227 ToStream.Write(_sendBuffer, 0, _sendBufferAt);
228 ToStream.Flush();
230 _sendBufferAt = 0;
231 _lastRowEndAt = 0;
232 _lastFieldEndAt = 0;
235 public void FlushRows()
237 if (_lastRowEndAt > 0)
239 ToStream.Write(_sendBuffer, 0, _lastRowEndAt);
240 ToStream.Flush();
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;
248 _lastRowEndAt = 0;
252 public void FlushFields()
254 if (_lastFieldEndAt > 0)
256 ToStream.Write(_sendBuffer, 0, _lastFieldEndAt);
257 ToStream.Flush();
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;
265 _lastFieldEndAt = 0;
269 public void Close()
271 if (_atField > 0)
273 EndRow();
275 Flush();
276 ToStream.Close();
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)
326 return
327 c == '\t'
328 ? esc_t
329 : c == '\n'
330 ? esc_n
331 : c == '\r'
332 ? esc_r
333 : c == '\b'
334 ? esc_b
335 : c == '\f'
336 ? esc_f
337 : c == '\v'
338 ? esc_v
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)
352 FlushRows();
353 if (len >= SpaceInBuffer)
355 FlushFields();
356 if (len >= SpaceInBuffer)
358 BufferSize = len;
364 protected void AddBytes(byte[] bytes)
366 MakeRoomForBytes(bytes.Length);
368 for (int i = 0; i < bytes.Length; i++)
370 _sendBuffer[_sendBufferAt++] = bytes[i];
374 public void EndRow()
376 if (_context != null)
378 while (_atField < _context.CurrentState.CopyFormat.FieldCount)
380 AddNull();
383 if (_context == null || ! _context.CurrentState.CopyFormat.IsBinary)
385 AddBytes(SeparatorBytes);
387 _lastRowEndAt = _sendBufferAt;
388 _atField = 0;
391 protected void PrefixField()
393 if (_atField > 0)
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;
406 _atField++;
409 public void AddNull()
411 PrefixField();
412 AddBytes(NullBytes);
413 FieldAdded();
416 public void AddString(String fieldValue)
418 PrefixField();
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)
431 escapeAt = i;
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);
450 bufferedUpto++;
453 FieldAdded();
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));