001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.store;
007:
008: import java.math.BigDecimal;
009: import java.sql.Date;
010: import java.sql.SQLException;
011: import java.sql.Time;
012: import java.sql.Timestamp;
013:
014: import org.h2.constant.SysProperties;
015: import org.h2.engine.Constants;
016: import org.h2.message.Message;
017: import org.h2.util.MathUtils;
018: import org.h2.value.Value;
019: import org.h2.value.ValueArray;
020: import org.h2.value.ValueBoolean;
021: import org.h2.value.ValueByte;
022: import org.h2.value.ValueBytes;
023: import org.h2.value.ValueDate;
024: import org.h2.value.ValueDecimal;
025: import org.h2.value.ValueDouble;
026: import org.h2.value.ValueFloat;
027: import org.h2.value.ValueInt;
028: import org.h2.value.ValueJavaObject;
029: import org.h2.value.ValueLob;
030: import org.h2.value.ValueLong;
031: import org.h2.value.ValueNull;
032: import org.h2.value.ValueShort;
033: import org.h2.value.ValueString;
034: import org.h2.value.ValueStringFixed;
035: import org.h2.value.ValueStringIgnoreCase;
036: import org.h2.value.ValueTime;
037: import org.h2.value.ValueTimestamp;
038: import org.h2.value.ValueUuid;
039:
040: /**
041: * A data page is a byte buffer that contains persistent data of a row or index
042: * page.
043: */
044: public abstract class DataPage {
045:
046: static final boolean CHECKSUM = true;
047:
048: /**
049: * The data handler responsible for lob objects.
050: */
051: protected DataHandler handler;
052:
053: /**
054: * The data itself.
055: */
056: protected byte[] data;
057:
058: /**
059: * The current write or read position.
060: */
061: protected int pos;
062:
063: /**
064: * Calculate the checksum and write.
065: *
066: */
067: public abstract void updateChecksum();
068:
069: /**
070: * Test if the checksum is correct.
071: *
072: * @param len the number of bytes
073: * @throws SQLException if the checksum does not match
074: */
075: public abstract void check(int len) throws SQLException;
076:
077: /**
078: * The space required for the checksum and additional fillers.
079: *
080: * @return the size
081: */
082: public abstract int getFillerLength();
083:
084: /**
085: * Update an integer at the given position.
086: * The current position is not change.
087: *
088: * @param pos the position
089: * @param x the value
090: */
091: public abstract void setInt(int pos, int x);
092:
093: /**
094: * Write an integer at the current position.
095: * The current position is incremented.
096: *
097: * @param x the value
098: */
099: public abstract void writeInt(int x);
100:
101: /**
102: * Read an integer at the current position.
103: * The current position is incremented.
104: *
105: * @return the value
106: */
107: public abstract int readInt();
108:
109: /**
110: * Get the length of an integer value.
111: *
112: * @return the length
113: */
114: public abstract int getIntLen();
115:
116: /**
117: * Get the length of a long value.
118: *
119: * @param x the value
120: * @return the length
121: */
122: public abstract int getLongLen(long x);
123:
124: /**
125: * Get the length of a String value.
126: *
127: * @param s the value
128: * @return the length
129: */
130: public abstract int getStringLen(String s);
131:
132: /**
133: * Read a String value.
134: * The current position is incremented.
135: *
136: * @return the value
137: */
138: public abstract String readString();
139:
140: /**
141: * Write a String value.
142: * The current position is incremented.
143: *
144: * @param s the value
145: */
146: public abstract void writeString(String s);
147:
148: /**
149: * Increase the size to the given length.
150: * The current position is set to the given value.
151: *
152: * @param len the new length
153: */
154: public abstract void fill(int len);
155:
156: public static DataPage create(DataHandler handler, int capacity) {
157: if (handler.getTextStorage()) {
158: return new DataPageText(handler, new byte[capacity]);
159: } else {
160: return new DataPageBinary(handler, new byte[capacity]);
161: }
162: }
163:
164: public static DataPage create(DataHandler handler, byte[] buff) {
165: if (handler.getTextStorage()) {
166: return new DataPageText(handler, buff);
167: } else {
168: return new DataPageBinary(handler, buff);
169: }
170: }
171:
172: protected DataPage(DataHandler handler, byte[] data) {
173: this .handler = handler;
174: this .data = data;
175: }
176:
177: public void checkCapacity(int plus) {
178: if (pos + plus >= data.length) {
179: byte[] d = new byte[(data.length + plus) * 2];
180: // must copy everything, because pos could be 0 and data may be
181: // still required
182: System.arraycopy(data, 0, d, 0, data.length);
183: data = d;
184: }
185: }
186:
187: public int length() {
188: return pos;
189: }
190:
191: public byte[] getBytes() {
192: return data;
193: }
194:
195: public void reset() {
196: pos = 0;
197: }
198:
199: public void writeDataPageNoSize(DataPage page) {
200: checkCapacity(page.pos);
201: // don't write filler
202: int len = page.pos - getFillerLength();
203: System.arraycopy(page.data, 0, data, pos, len);
204: pos += len;
205: }
206:
207: public DataPage readDataPageNoSize() {
208: int len = data.length - pos;
209: DataPage page = DataPage.create(handler, len);
210: System.arraycopy(data, pos, page.data, 0, len);
211: page.pos = len;
212: return page;
213: }
214:
215: public void write(byte[] buff, int off, int len) {
216: checkCapacity(len);
217: System.arraycopy(buff, 0, data, pos, len);
218: pos += len;
219: }
220:
221: public void read(byte[] buff, int off, int len) {
222: System.arraycopy(data, pos, buff, off, len);
223: pos += len;
224: }
225:
226: public void writeByte(byte x) {
227: data[pos++] = x;
228: }
229:
230: public int readByte() {
231: return data[pos++];
232: }
233:
234: public long readLong() {
235: return ((long) (readInt()) << 32) + (readInt() & 0xffffffffL);
236: }
237:
238: public void writeLong(long x) {
239: writeInt((int) (x >>> 32));
240: writeInt((int) x);
241: }
242:
243: public void writeValue(Value v) throws SQLException {
244: if (SysProperties.CHECK) {
245: checkCapacity(8);
246: }
247: // TODO text output: could be in the Value... classes
248: if (v == ValueNull.INSTANCE) {
249: data[pos++] = '-';
250: return;
251: }
252: int start = pos;
253: data[pos++] = (byte) (v.getType() + 'a');
254: switch (v.getType()) {
255: case Value.BOOLEAN:
256: case Value.BYTE:
257: case Value.SHORT:
258: case Value.INT:
259: writeInt(v.getInt());
260: break;
261: case Value.LONG:
262: writeLong(v.getLong());
263: break;
264: case Value.DECIMAL:
265: String s = v.getString();
266: writeString(s);
267: break;
268: case Value.TIME:
269: writeLong(v.getTimeNoCopy().getTime());
270: break;
271: case Value.DATE:
272: writeLong(v.getDateNoCopy().getTime());
273: break;
274: case Value.TIMESTAMP: {
275: Timestamp ts = v.getTimestampNoCopy();
276: writeLong(ts.getTime());
277: writeInt(ts.getNanos());
278: break;
279: }
280: case Value.JAVA_OBJECT:
281: case Value.BYTES: {
282: byte[] b = v.getBytesNoCopy();
283: writeInt(b.length);
284: write(b, 0, b.length);
285: break;
286: }
287: case Value.UUID: {
288: ValueUuid uuid = (ValueUuid) v;
289: writeLong(uuid.getHigh());
290: writeLong(uuid.getLow());
291: break;
292: }
293: case Value.STRING:
294: case Value.STRING_IGNORECASE:
295: case Value.STRING_FIXED:
296: writeString(v.getString());
297: break;
298: case Value.DOUBLE:
299: writeLong(Double.doubleToLongBits(v.getDouble()));
300: break;
301: case Value.FLOAT:
302: writeInt(Float.floatToIntBits(v.getFloat()));
303: break;
304: case Value.BLOB:
305: case Value.CLOB: {
306: ValueLob lob = (ValueLob) v;
307: lob.convertToFileIfRequired(handler);
308: byte[] small = lob.getSmall();
309: if (small == null) {
310: // -2 for historical reasons (-1 didn't store precision)
311: int type = -2;
312: if (!lob.isLinked()) {
313: type = -3;
314: }
315: writeInt(type);
316: writeInt(lob.getTableId());
317: writeInt(lob.getObjectId());
318: writeLong(lob.getPrecision());
319: writeByte((byte) (lob.useCompression() ? 1 : 0));
320: if (type == -3) {
321: writeString(lob.getFileName());
322: }
323: } else {
324: writeInt(small.length);
325: write(small, 0, small.length);
326: }
327: break;
328: }
329: case Value.ARRAY: {
330: Value[] list = ((ValueArray) v).getList();
331: writeInt(list.length);
332: for (int i = 0; i < list.length; i++) {
333: writeValue(list[i]);
334: }
335: break;
336: }
337: default:
338: throw Message.getInternalError("type=" + v.getType());
339: }
340: if (SysProperties.CHECK2) {
341: if (pos - start != getValueLen(v)) {
342: throw Message
343: .getInternalError("value size error: got "
344: + (pos - start) + " expected "
345: + getValueLen(v));
346: }
347: }
348: }
349:
350: public int getValueLen(Value v) throws SQLException {
351: if (v == ValueNull.INSTANCE) {
352: return 1;
353: }
354: switch (v.getType()) {
355: case Value.BOOLEAN:
356: case Value.BYTE:
357: case Value.SHORT:
358: case Value.INT:
359: return 1 + getIntLen();
360: case Value.LONG:
361: return 1 + getLongLen(v.getLong());
362: case Value.DOUBLE:
363: return 1 + getLongLen(Double
364: .doubleToLongBits(v.getDouble()));
365: case Value.FLOAT:
366: return 1 + getIntLen();
367: case Value.STRING:
368: case Value.STRING_IGNORECASE:
369: case Value.STRING_FIXED:
370: return 1 + getStringLen(v.getString());
371: case Value.DECIMAL:
372: return 1 + getStringLen(v.getString());
373: case Value.JAVA_OBJECT:
374: case Value.BYTES: {
375: int len = v.getBytesNoCopy().length;
376: return 1 + getIntLen() + len;
377: }
378: case Value.UUID: {
379: ValueUuid uuid = (ValueUuid) v;
380: return 1 + getLongLen(uuid.getHigh())
381: + getLongLen(uuid.getLow());
382: }
383: case Value.TIME:
384: return 1 + getLongLen(v.getTimeNoCopy().getTime());
385: case Value.DATE:
386: return 1 + getLongLen(v.getDateNoCopy().getTime());
387: case Value.TIMESTAMP: {
388: Timestamp ts = v.getTimestampNoCopy();
389: return 1 + getLongLen(ts.getTime()) + getIntLen();
390: }
391: case Value.BLOB:
392: case Value.CLOB: {
393: int len = 1;
394: ValueLob lob = (ValueLob) v;
395: lob.convertToFileIfRequired(handler);
396: byte[] small = lob.getSmall();
397: if (small != null) {
398: len += getIntLen() + small.length;
399: } else {
400: len += getIntLen() + getIntLen() + getIntLen()
401: + getLongLen(lob.getPrecision()) + 1;
402: if (!lob.isLinked()) {
403: len += getStringLen(lob.getFileName());
404: }
405: }
406: return len;
407: }
408: case Value.ARRAY: {
409: Value[] list = ((ValueArray) v).getList();
410: int len = 1 + getIntLen();
411: for (int i = 0; i < list.length; i++) {
412: len += getValueLen(list[i]);
413: }
414: return len;
415: }
416: default:
417: throw Message.getInternalError("type=" + v.getType());
418: }
419: }
420:
421: public Value readValue() throws SQLException {
422: int dataType = data[pos++];
423: if (dataType == '-') {
424: return ValueNull.INSTANCE;
425: }
426: dataType = (dataType - 'a');
427: switch (dataType) {
428: case Value.BOOLEAN:
429: return ValueBoolean.get(readInt() == 1);
430: case Value.BYTE:
431: return ValueByte.get((byte) readInt());
432: case Value.SHORT:
433: return ValueShort.get((short) readInt());
434: case Value.INT:
435: return ValueInt.get(readInt());
436: case Value.LONG:
437: return ValueLong.get(readLong());
438: case Value.DECIMAL:
439: return ValueDecimal.get(new BigDecimal(readString()));
440: case Value.DATE:
441: return ValueDate.getNoCopy(new Date(readLong()));
442: case Value.TIME:
443: // need to normalize the year, month and day
444: return ValueTime.get(new Time(readLong()));
445: case Value.TIMESTAMP: {
446: Timestamp ts = new Timestamp(readLong());
447: ts.setNanos(readInt());
448: return ValueTimestamp.getNoCopy(ts);
449: }
450: case Value.JAVA_OBJECT: {
451: int len = readInt();
452: byte[] b = new byte[len];
453: read(b, 0, len);
454: return ValueJavaObject.getNoCopy(b);
455: }
456: case Value.BYTES: {
457: int len = readInt();
458: byte[] b = new byte[len];
459: read(b, 0, len);
460: return ValueBytes.getNoCopy(b);
461: }
462: case Value.UUID:
463: return ValueUuid.get(readLong(), readLong());
464: case Value.STRING:
465: return ValueString.get(readString());
466: case Value.STRING_IGNORECASE:
467: return ValueStringIgnoreCase.get(readString());
468: case Value.STRING_FIXED:
469: return ValueStringFixed.get(readString());
470: case Value.DOUBLE:
471: return ValueDouble.get(Double.longBitsToDouble(readLong()));
472: case Value.FLOAT:
473: return ValueFloat.get(Float.intBitsToFloat(readInt()));
474: case Value.BLOB:
475: case Value.CLOB: {
476: int smallLen = readInt();
477: if (smallLen >= 0) {
478: byte[] small = new byte[smallLen];
479: read(small, 0, smallLen);
480: return ValueLob.createSmallLob(dataType, small);
481: } else {
482: int tableId = readInt();
483: int objectId = readInt();
484: long precision = 0;
485: boolean compression = false;
486: // -2 is for historical reasons (-1 didn't store precision)
487: if (smallLen == -2 || smallLen == -3) {
488: precision = readLong();
489: compression = readByte() == 1;
490: }
491: ValueLob lob = ValueLob.open(dataType, handler,
492: tableId, objectId, precision, compression);
493: if (smallLen == -3) {
494: lob.setFileName(readString());
495: }
496: return lob;
497: }
498: }
499: case Value.ARRAY: {
500: int len = readInt();
501: Value[] list = new Value[len];
502: for (int i = 0; i < len; i++) {
503: list[i] = readValue();
504: }
505: return ValueArray.get(list);
506: }
507: default:
508: throw Message.getInternalError("type=" + dataType);
509: }
510: }
511:
512: public void fillAligned() {
513: // TODO datapage: fillAligned should not use a fixed constant '2'
514: // 0..6 > 8, 7..14 > 16, 15..22 > 24, ...
515: fill(MathUtils.roundUp(pos + 2, Constants.FILE_BLOCK_SIZE));
516: }
517:
518: public void setPos(int pos) {
519: this.pos = pos;
520: }
521:
522: }
|