1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.index; |
7 | |
8 | import java.util.ArrayList; |
9 | import org.h2.engine.Session; |
10 | import org.h2.message.DbException; |
11 | import org.h2.result.Row; |
12 | import org.h2.result.SearchRow; |
13 | import org.h2.result.SortOrder; |
14 | import org.h2.table.Column; |
15 | import org.h2.table.IndexColumn; |
16 | import org.h2.table.RegularTable; |
17 | import org.h2.table.TableFilter; |
18 | import org.h2.util.New; |
19 | import org.h2.util.ValueHashMap; |
20 | import org.h2.value.Value; |
21 | |
22 | /** |
23 | * A non-unique index based on an in-memory hash map. |
24 | * |
25 | * @author Sergi Vladykin |
26 | */ |
27 | public class NonUniqueHashIndex extends BaseIndex { |
28 | |
29 | /** |
30 | * The index of the indexed column. |
31 | */ |
32 | private final int indexColumn; |
33 | private ValueHashMap<ArrayList<Long>> rows; |
34 | private final RegularTable tableData; |
35 | private long rowCount; |
36 | |
37 | public NonUniqueHashIndex(RegularTable table, int id, String indexName, |
38 | IndexColumn[] columns, IndexType indexType) { |
39 | initBaseIndex(table, id, indexName, columns, indexType); |
40 | this.indexColumn = columns[0].column.getColumnId(); |
41 | this.tableData = table; |
42 | reset(); |
43 | } |
44 | |
45 | private void reset() { |
46 | rows = ValueHashMap.newInstance(); |
47 | rowCount = 0; |
48 | } |
49 | |
50 | @Override |
51 | public void truncate(Session session) { |
52 | reset(); |
53 | } |
54 | |
55 | @Override |
56 | public void add(Session session, Row row) { |
57 | Value key = row.getValue(indexColumn); |
58 | ArrayList<Long> positions = rows.get(key); |
59 | if (positions == null) { |
60 | positions = New.arrayList(); |
61 | rows.put(key, positions); |
62 | } |
63 | positions.add(row.getKey()); |
64 | rowCount++; |
65 | } |
66 | |
67 | @Override |
68 | public void remove(Session session, Row row) { |
69 | if (rowCount == 1) { |
70 | // last row in table |
71 | reset(); |
72 | } else { |
73 | Value key = row.getValue(indexColumn); |
74 | ArrayList<Long> positions = rows.get(key); |
75 | if (positions.size() == 1) { |
76 | // last row with such key |
77 | rows.remove(key); |
78 | } else { |
79 | positions.remove(row.getKey()); |
80 | } |
81 | rowCount--; |
82 | } |
83 | } |
84 | |
85 | @Override |
86 | public Cursor find(Session session, SearchRow first, SearchRow last) { |
87 | if (first == null || last == null) { |
88 | throw DbException.throwInternalError(); |
89 | } |
90 | if (first != last) { |
91 | if (compareKeys(first, last) != 0) { |
92 | throw DbException.throwInternalError(); |
93 | } |
94 | } |
95 | Value v = first.getValue(indexColumn); |
96 | /* |
97 | * Sometimes the incoming search is a similar, but not the same type |
98 | * e.g. the search value is INT, but the index column is LONG. In which |
99 | * case we need to convert, otherwise the ValueHashMap will not find the |
100 | * result. |
101 | */ |
102 | v = v.convertTo(tableData.getColumn(indexColumn).getType()); |
103 | ArrayList<Long> positions = rows.get(v); |
104 | return new NonUniqueHashCursor(session, tableData, positions); |
105 | } |
106 | |
107 | @Override |
108 | public long getRowCount(Session session) { |
109 | return rowCount; |
110 | } |
111 | |
112 | @Override |
113 | public long getRowCountApproximation() { |
114 | return rowCount; |
115 | } |
116 | |
117 | @Override |
118 | public long getDiskSpaceUsed() { |
119 | return 0; |
120 | } |
121 | |
122 | @Override |
123 | public void close(Session session) { |
124 | // nothing to do |
125 | } |
126 | |
127 | @Override |
128 | public void remove(Session session) { |
129 | // nothing to do |
130 | } |
131 | |
132 | @Override |
133 | public double getCost(Session session, int[] masks, TableFilter filter, |
134 | SortOrder sortOrder) { |
135 | for (Column column : columns) { |
136 | int index = column.getColumnId(); |
137 | int mask = masks[index]; |
138 | if ((mask & IndexCondition.EQUALITY) != IndexCondition.EQUALITY) { |
139 | return Long.MAX_VALUE; |
140 | } |
141 | } |
142 | return 2; |
143 | } |
144 | |
145 | @Override |
146 | public void checkRename() { |
147 | // ok |
148 | } |
149 | |
150 | @Override |
151 | public boolean needRebuild() { |
152 | return true; |
153 | } |
154 | |
155 | @Override |
156 | public boolean canGetFirstOrLast() { |
157 | return false; |
158 | } |
159 | |
160 | @Override |
161 | public Cursor findFirstOrLast(Session session, boolean first) { |
162 | throw DbException.getUnsupportedException("HASH"); |
163 | } |
164 | |
165 | @Override |
166 | public boolean canScan() { |
167 | return false; |
168 | } |
169 | |
170 | } |