001/* 002 * PermissionsEx 003 * Copyright (C) zml and PermissionsEx contributors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package ca.stellardrift.permissionsex.datastore.sql; 018 019import ca.stellardrift.permissionsex.datastore.sql.dao.LegacyDao; 020import ca.stellardrift.permissionsex.datastore.sql.dao.LegacyMigration; 021import ca.stellardrift.permissionsex.impl.util.PCollections; 022import ca.stellardrift.permissionsex.subject.SubjectRef; 023import ca.stellardrift.permissionsex.context.ContextValue; 024import ca.stellardrift.permissionsex.rank.RankLadder; 025import org.checkerframework.checker.nullness.qual.Nullable; 026import org.pcollections.PMap; 027import org.pcollections.PSet; 028import org.pcollections.PVector; 029import org.spongepowered.configurate.util.CheckedSupplier; 030 031import java.io.BufferedReader; 032import java.io.IOException; 033import java.io.InputStream; 034import java.io.InputStreamReader; 035import java.nio.charset.StandardCharsets; 036import java.sql.Connection; 037import java.sql.PreparedStatement; 038import java.sql.ResultSet; 039import java.sql.SQLException; 040import java.sql.Statement; 041import java.sql.Types; 042import java.util.List; 043import java.util.Map; 044import java.util.Optional; 045import java.util.Set; 046 047/** 048 * Abstraction to communicate with the SQL database. Instances are not thread-safe -- it's best to create a new one for each operation on a single thread 049 */ 050public abstract class SqlDao implements AutoCloseable { 051 private final Connection conn; 052 private final SqlDataStore ds; 053 int holdOpen, transactionLevel; 054 055 protected SqlDao(SqlDataStore ds) throws SQLException { 056 this.ds = ds; 057 this.conn = ds.getDataSource().getConnection(); 058 } 059 060 // -- Queries 061 062 protected String getSelectGlobalParameterQuery() { 063 return "SELECT (`value`) FROM {}global WHERE `key`=?"; 064 } 065 066 protected abstract String getInsertGlobalParameterQueryUpdating(); 067 068 protected String getDeleteGlobalParameterQuery() { 069 return "DELETE FROM {}global WHERE `key`=?"; 070 } 071 072 protected String getGetSubjectRefIdQuery() { 073 return "SELECT type, identifier FROM {}subjects WHERE id=?"; 074 } 075 076 protected String getGetSubjectRefTypeNameQuery() { 077 return "SELECT id FROM {}subjects WHERE type=? AND identifier=?"; 078 } 079 080 protected String getDeleteSubjectIdQuery() { 081 return "DELETE FROM {}subjects WHERE id=?"; 082 } 083 084 protected String getDeleteSubjectTypeNameQuery() { 085 return "DELETE FROM {}subjects WHERE type=? AND identifier=?"; 086 } 087 088 protected String getInsertSubjectTypeNameQuery() { 089 return "INSERT INTO {}subjects (type, identifier) VALUES (?, ?)"; 090 } 091 092 protected String getSelectContextsSegmentQuery() { 093 return "SELECT `key`, `value` FROM {}contexts WHERE segment=?"; 094 } 095 096 protected String getSelectSegmentsSubjectQuery() { 097 return "SELECT id, perm_default FROM {}segments WHERE subject=?"; 098 } 099 100 protected String getSelectPermissionsSegmentQuery() { 101 return "SELECT `key`, `value` FROM {}permissions WHERE segment=?"; 102 } 103 104 protected String getSelectOptionsSegmentQuery() { 105 return "SELECT `key`, `value` FROM {}options WHERE segment=?"; 106 } 107 108 protected String getSelectInheritanceSegmentQuery() { 109 return "SELECT * FROM {}inheritance LEFT JOIN ({}subjects) on ({}inheritance.parent={}subjects.id) WHERE segment=?"; 110 } 111 112 protected String getInsertSegmentQuery() { 113 return "INSERT INTO {}segments (subject, perm_default) VALUES (?, ?)"; 114 } 115 116 protected String getDeleteSegmentIdQuery() { 117 return "DELETE FROM {}segments WHERE id=?"; 118 } 119 120 protected String getSelectSubjectIdentifiersQuery() { 121 return "SELECT identifier FROM {}subjects WHERE type=?"; 122 } 123 124 protected String getSelectSubjectTypesQuery() { 125 return "SELECT DISTINCT type FROM {}subjects"; 126 } 127 128 protected String getDeleteOptionKeyQuery() { 129 return "DELETE FROM {}options WHERE segment=? AND `key`=?"; 130 } 131 132 protected String getDeleteOptionsQuery() { 133 return "DELETE FROM {}options WHERE segment=?"; 134 } 135 136 protected abstract String getInsertOptionUpdatingQuery(); 137 138 protected abstract String getInsertPermissionUpdatingQuery(); 139 140 protected String getDeletePermissionKeyQuery() { 141 return "DELETE FROM {}permissions WHERE segment=? AND `key`=?"; 142 } 143 144 protected String getDeletePermissionsQuery() { 145 return "DELETE FROM {}permissions WHERE segment=?"; 146 } 147 148 protected String getUpdatePermissionDefaultQuery() { 149 return "UPDATE {}segments SET perm_default=? WHERE id=?"; 150 } 151 152 protected String getInsertInheritanceQuery() { 153 return "INSERT INTO {}inheritance (`segment`, `parent`) VALUES (?, ?)"; 154 } 155 156 protected String getDeleteInheritanceParentQuery() { 157 return "DELETE FROM {}inheritance WHERE segment=? AND parent=?"; 158 } 159 160 protected String getDeleteInheritanceQuery() { 161 return "DELETE FROM {}inheritance WHERE segment=?"; 162 } 163 164 protected String getInsertContextQuery() { 165 return "INSERT INTO {}contexts (segment, `key`, `value`) VALUES (?, ?, ?)"; 166 } 167 168 protected String getDeleteContextQuery() { 169 return "DELETE FROM {}contexts WHERE segment=?"; 170 } 171 172 protected String getSelectContextInheritanceQuery() { 173 return "SELECT `child_key`, `child_value`, `parent_key`, `parent_value` FROM {}context_inheritance ORDER BY `child_key`, `child_value`, `id` ASC"; 174 } 175 176 protected String getInsertContextInheritanceQuery() { 177 return "INSERT INTO {}context_inheritance (child_key, child_value, parent_key, parent_value) VALUES (?, ?, ?, ?)"; 178 } 179 180 protected String getDeleteContextInheritanceQuery() { 181 return "DELETE FROM {}context_inheritance WHERE child_key=? AND child_value=?"; 182 } 183 184 protected String getSelectRankLadderQuery() { 185 return "SELECT `{}rank_ladders`.`id`, `subject`, `type`, `identifier` FROM {}rank_ladders LEFT JOIN (`{}subjects`) ON (`{}rank_ladders`.`subject`=`{}subjects`.`id`) WHERE `name`=? ORDER BY `{}rank_ladders`.`id` ASC"; 186 } 187 188 protected String getTestRankLadderExistsQuery() { 189 return "SELECT `id` FROM {}rank_ladders WHERE `name`=? LIMIT 1"; 190 } 191 192 protected String getInsertRankLadderQuery() { 193 return "INSERT INTO {}rank_ladders (`name`, `subject`) VALUES (?, ?)"; 194 } 195 196 protected String getDeleteRankLadderQuery() { 197 return "DELETE FROM {}rank_ladders WHERE `name`=?"; 198 } 199 200 protected String getSelectAllRankLadderNamesQuery() { 201 return "SELECT DISTINCT `name` FROM {}rank_ladders"; 202 } 203 204 protected String getSelectAllSubjectsQuery() { 205 return "SELECT `id`, `type`, `identifier` FROM {}subjects"; 206 } 207 208 protected String getRenameTableQuery() { 209 return "ALTER TABLE ? RENAME ?"; 210 } 211 212 protected String getSelectAllContextKeysUniqueQuery() { 213 return "SELECT DISTINCT `key` FROM {}contexts"; 214 } 215 216 public PreparedStatement prepareStatement(String query) throws SQLException { 217 return conn.prepareStatement(this.ds.insertPrefix(query)); 218 } 219 220 protected PreparedStatement prepareStatement(String query, int params) throws SQLException { 221 return conn.prepareStatement(this.ds.insertPrefix(query), params); 222 } 223 224 protected <T> T executeInTransaction(CheckedSupplier<T, SQLException> func) throws SQLException { 225 transactionLevel++; 226 conn.setAutoCommit(false); 227 try { 228 T ret = func.get(); 229 if (--transactionLevel <= 0) { 230 conn.commit(); 231 } 232 return ret; 233 } finally { 234 if (transactionLevel <= 0) { 235 conn.setAutoCommit(true); 236 } 237 } 238 } 239 240 // -- Operations 241 242 public LegacyDao legacy() { 243 return LegacyDao.INSTANCE; 244 } 245 246 public Optional<String> getGlobalParameter(String key) throws SQLException { 247 try (PreparedStatement stmt = prepareStatement(getSelectGlobalParameterQuery())) { 248 stmt.setString(1, key); 249 ResultSet rs = stmt.executeQuery(); 250 if (rs.next()) { 251 return Optional.of(rs.getString(1)); 252 } else { 253 return Optional.empty(); 254 } 255 } 256 257 } 258 259 public void setGlobalParameter(String key, final @Nullable String value) throws SQLException { 260 if (value == null) { 261 try (PreparedStatement stmt = prepareStatement(getDeleteGlobalParameterQuery())) { 262 stmt.setString(1, key); 263 stmt.executeUpdate(); 264 } 265 } else { 266 try (PreparedStatement stmt = prepareStatement(getInsertGlobalParameterQueryUpdating())) { 267 stmt.setString(1, key); 268 stmt.setString(2, value); 269 stmt.executeUpdate(); 270 } 271 } 272 } 273 274 public Optional<SqlSubjectRef<?>> getSubjectRef(int id) throws SQLException { 275 try (PreparedStatement stmt = prepareStatement(getGetSubjectRefIdQuery())) { 276 stmt.setInt(1, id); 277 ResultSet res = stmt.executeQuery(); 278 279 if (!res.next()) { 280 return Optional.empty(); 281 } 282 return Optional.of(new SqlSubjectRef<>(this.ds.ctx(), id, res.getString(1), res.getString(2))); 283 } 284 } 285 286 public Optional<SqlSubjectRef<?>> getSubjectRef(String type, String name) throws SQLException { 287 try (PreparedStatement stmt = prepareStatement(getGetSubjectRefTypeNameQuery())) { 288 stmt.setString(1, type); 289 stmt.setString(2, name); 290 ResultSet res = stmt.executeQuery(); 291 292 if (!res.next()) { 293 return Optional.empty(); 294 } 295 return Optional.of(new SqlSubjectRef<>(this.ds.ctx(), res.getInt(1), type, name)); 296 } 297 } 298 299 public boolean removeSubject(SqlSubjectRef<?> ref) throws SQLException { 300 try (PreparedStatement stmt = prepareStatement(getDeleteSubjectIdQuery())) { 301 stmt.setInt(1, ref.id()); 302 return stmt.executeUpdate() > 0; 303 } 304 } 305 306 public boolean removeSubject(String type, String name) throws SQLException { 307 try (PreparedStatement stmt = prepareStatement(getDeleteSubjectTypeNameQuery())) { 308 stmt.setString(1, type); 309 stmt.setString(2, name); 310 return stmt.executeUpdate() > 0; 311 } 312 } 313 314 public SqlSubjectRef<?> getOrCreateSubjectRef(String type, String name) throws SQLException { 315 final SqlSubjectRef<?> ret = SqlSubjectRef.unresolved(this.ds.ctx(), type, name); 316 allocateSubjectRef(ret); 317 return ret; 318 } 319 320 public void allocateSubjectRef(final SqlSubjectRef<?> ref) throws SQLException { 321 executeInTransaction(() -> { 322 try (PreparedStatement stmt = prepareStatement(getGetSubjectRefTypeNameQuery())) { 323 stmt.setString(1, ref.rawType()); 324 stmt.setString(2, ref.rawIdentifier()); 325 ResultSet res = stmt.executeQuery(); 326 if (res.next()) { 327 ref.id(res.getInt(1)); 328 } else { 329 try (PreparedStatement addStatement = prepareStatement(getInsertSubjectTypeNameQuery(), Statement.RETURN_GENERATED_KEYS)) { 330 addStatement.setString(1, ref.rawType()); 331 addStatement.setString(2, ref.rawIdentifier()); 332 addStatement.executeUpdate(); 333 res = addStatement.getGeneratedKeys(); 334 res.next(); 335 ref.id(res.getInt(1)); 336 } 337 } 338 } 339 return ref; 340 }); 341 } 342 343 public int getIdAllocating(final SqlSubjectRef<?> ref) throws SQLException { 344 if (ref.isUnallocated()) { 345 allocateSubjectRef(ref); 346 } 347 return ref.id(); 348 } 349 350 351 private PSet<ContextValue<?>> getSegmentContexts(final int segmentId) throws SQLException { 352 try (PreparedStatement stmt = prepareStatement(getSelectContextsSegmentQuery())) { 353 stmt.setInt(1, segmentId); 354 PSet<ContextValue<?>> result = PCollections.set(); 355 356 ResultSet rs = stmt.executeQuery(); 357 while (rs.next()) { 358 result = result.plus(new ContextValue<>(rs.getString(1), rs.getString(2))); 359 } 360 return result; 361 } 362 } 363 364 public List<SqlSegment> getSegments(SqlSubjectRef<?> ref) throws SQLException { 365 PVector<SqlSegment> result = PCollections.vector(); 366 try (PreparedStatement stmt = prepareStatement(getSelectSegmentsSubjectQuery())) { 367 stmt.setInt(1, getIdAllocating(ref)); 368 ResultSet rs = stmt.executeQuery(); 369 370 while (rs.next()) { 371 final int id = rs.getInt(1); 372 Number permDef = (Number) rs.getObject(2); 373 PSet<ContextValue<?>> contexts = getSegmentContexts(id); 374 375 PMap<String, Integer> permValues = PCollections.map(); 376 PMap<String, String> optionValues = PCollections.map(); 377 PVector<SqlSubjectRef<?>> inheritanceValues = PCollections.vector(); 378 379 try (PreparedStatement permStmt = prepareStatement(getSelectPermissionsSegmentQuery())) { 380 permStmt.setInt(1, id); 381 382 ResultSet segmentRs = permStmt.executeQuery(); 383 while (segmentRs.next()) { 384 permValues = permValues.plus(segmentRs.getString(1), segmentRs.getInt(2)); 385 } 386 } 387 388 try (PreparedStatement optStmt = prepareStatement(getSelectOptionsSegmentQuery())) { 389 optStmt.setInt(1, id); 390 391 ResultSet segmentRs = optStmt.executeQuery(); 392 while (segmentRs.next()) { 393 optionValues = optionValues.plus(segmentRs.getString(1), segmentRs.getString(2)); 394 } 395 } 396 397 try (PreparedStatement inheritStmt = prepareStatement(getSelectInheritanceSegmentQuery())) { 398 inheritStmt.setInt(1, id); 399 400 ResultSet segmentRs = inheritStmt.executeQuery(); 401 while (segmentRs.next()) { 402 inheritanceValues = inheritanceValues.plus(new SqlSubjectRef<>(this.ds.ctx(), segmentRs.getInt(3), segmentRs.getString(4), segmentRs.getString(5))); 403 } 404 } 405 406 result = result.plus(new SqlSegment(id, contexts, permValues, optionValues, inheritanceValues, permDef == null ? null : permDef.intValue(), PCollections.vector())); 407 408 } 409 } 410 return result; 411 } 412 413 public SqlSegment addSegment(final SqlSubjectRef<?> ref) throws SQLException { // TODO: Is this method useful? 414 final SqlSegment segment = SqlSegment.unallocated(); 415 allocateSegment(ref, segment); 416 return segment; 417 } 418 419 public void updateFullSegment(SqlSubjectRef<?> ref, SqlSegment segment) throws SQLException { 420 executeInTransaction(() -> { 421 allocateSegment(ref, segment); 422 setContexts(segment, segment.contexts()); 423 setOptions(segment, segment.options()); 424 setParents(segment, segment.parents()); 425 setPermissions(segment, segment.permissions()); 426 setDefaultValue(segment, segment.fallbackPermission()); 427 return null; 428 }); 429 } 430 431 public void setContexts(SqlSegment seg, PSet<ContextValue<?>> contexts) throws SQLException { 432 // Update contexts 433 executeInTransaction(() -> { 434 try (PreparedStatement delete = prepareStatement(getDeleteContextQuery()); 435 PreparedStatement insert = prepareStatement(getInsertContextQuery())) { 436 delete.setInt(1, seg.id()); 437 delete.executeUpdate(); 438 439 insert.setInt(1, seg.id()); 440 441 for (ContextValue<?> context : contexts) { 442 insert.setString(2, context.key()); 443 insert.setString(3, context.rawValue()); 444 insert.addBatch(); 445 } 446 insert.executeBatch(); 447 } 448 return null; 449 }); 450 451 } 452 453 public void allocateSegment(SqlSubjectRef<?> subject, SqlSegment val) throws SQLException { 454 if (!val.isUnallocated()) { 455 return; 456 } 457 458 try (PreparedStatement stmt = prepareStatement(getInsertSegmentQuery(), Statement.RETURN_GENERATED_KEYS)) { 459 stmt.setInt(1, getIdAllocating(subject)); 460 if (val.fallbackPermission() == 0) { 461 stmt.setNull(2, Types.INTEGER); 462 } else { 463 stmt.setInt(2, val.fallbackPermission()); 464 } 465 466 stmt.executeUpdate(); 467 ResultSet res = stmt.getGeneratedKeys(); 468 res.next(); 469 val.id(res.getInt(1)); 470 } 471 setContexts(val, val.contexts()); 472 } 473 474 public boolean removeSegment(SqlSegment segment) throws SQLException { 475 try (PreparedStatement stmt = prepareStatement(getDeleteSegmentIdQuery())) { 476 stmt.setInt(1, segment.id()); 477 return stmt.executeUpdate() > 0; 478 } 479 } 480 481 public Set<String> getAllIdentifiers(String type) throws SQLException { 482 try (PreparedStatement stmt = prepareStatement(getSelectSubjectIdentifiersQuery())) { 483 stmt.setString(1, type); 484 485 ResultSet rs = stmt.executeQuery(); 486 PSet<String> ret = PCollections.set(); 487 488 while (rs.next()) { 489 ret = ret.plus(rs.getString(1)); 490 } 491 492 493 return ret; 494 } 495 } 496 497 public Set<String> getRegisteredTypes() throws SQLException { 498 try (ResultSet rs = prepareStatement(getSelectSubjectTypesQuery()).executeQuery()) { 499 PSet<String> ret = PCollections.set(); 500 501 while (rs.next()) { 502 ret = ret.plus(rs.getString(1)); 503 } 504 505 return ret; 506 } 507 } 508 509 public void initializeTables() throws SQLException { 510 if (hasTable("permissions")) { 511 return; 512 } 513 String database = conn.getMetaData().getDatabaseProductName().toLowerCase(); 514 try (InputStream res = SqlDao.class.getResourceAsStream("deploy/" + database + ".sql")) { 515 if (res == null) { 516 throw new SQLException("No initial schema available for " + database + " databases!"); 517 } 518 try (BufferedReader read = new BufferedReader(new InputStreamReader(res, StandardCharsets.UTF_8))) { 519 executeStream(read); 520 } 521 } catch (IOException e) { 522 throw new SQLException(e); 523 } 524 } 525 526 void executeStream(BufferedReader reader) throws SQLException, IOException { 527 try (Statement stmt = conn.createStatement()) { 528 StringBuilder currentQuery = new StringBuilder(); 529 String line; 530 while ((line = reader.readLine()) != null) { 531 if (line.startsWith("--")) { 532 continue; 533 } 534 535 currentQuery.append(line); 536 if (line.endsWith(";")) { 537 currentQuery.deleteCharAt(currentQuery.length() - 1); 538 String queryLine = currentQuery.toString().trim(); 539 currentQuery = new StringBuilder(); 540 if (!queryLine.isEmpty()) { 541 stmt.addBatch(ds.insertPrefix(queryLine)); 542 } 543 } 544 } 545 stmt.executeBatch(); 546 } 547 548 } 549 550 private boolean hasTable(String table) throws SQLException { 551 return conn.getMetaData().getTables(null, null, this.ds.getTableName(table).toUpperCase(), null).next(); // Upper-case for H2 552 } 553 554 public void clearOption(SqlSegment segment, String option) throws SQLException { 555 try (PreparedStatement stmt = prepareStatement(getDeleteOptionKeyQuery())) { 556 stmt.setInt(1, segment.id()); 557 stmt.setString(2, option); 558 stmt.executeUpdate(); 559 } 560 } 561 562 public void setOptions(final SqlSegment seg, final @Nullable Map<String, String> options) throws SQLException { 563 executeInTransaction(() -> { 564 try (PreparedStatement del = prepareStatement(getDeleteOptionsQuery()); 565 PreparedStatement ins = prepareStatement(getInsertOptionUpdatingQuery())) { 566 del.setInt(1, seg.id()); 567 del.executeUpdate(); 568 569 if (options != null) { 570 ins.setInt(1, seg.id()); 571 for (Map.Entry<String, String> ent : options.entrySet()) { 572 ins.setString(2, ent.getKey()); 573 ins.setString(3, ent.getValue()); 574 ins.addBatch(); 575 } 576 ins.executeBatch(); 577 } 578 } 579 return null; 580 }); 581 } 582 583 public void setOption(SqlSegment segment, String key, String value) throws SQLException { 584 try (PreparedStatement stmt = prepareStatement(getInsertOptionUpdatingQuery())) { 585 stmt.setInt(1, segment.id()); 586 stmt.setString(2, key); 587 stmt.setString(3, value); 588 stmt.executeUpdate(); 589 } 590 } 591 592 public void setPermission(SqlSegment segment, String permission, int value) throws SQLException { 593 try (PreparedStatement stmt = prepareStatement(getInsertPermissionUpdatingQuery())) { 594 stmt.setInt(1, segment.id()); 595 stmt.setString(2, permission); 596 stmt.setInt(3, value); 597 stmt.executeUpdate(); 598 } 599 600 } 601 602 public void clearPermission(SqlSegment segment, String permission) throws SQLException { 603 try (PreparedStatement stmt = prepareStatement(getDeletePermissionKeyQuery())) { 604 stmt.setInt(1, segment.id()); 605 stmt.setString(2, permission); 606 stmt.executeUpdate(); 607 } 608 } 609 610 public void setPermissions(final SqlSegment segment, final @Nullable Map<String, Integer> permissions) throws SQLException { 611 executeInTransaction(() -> { 612 try (PreparedStatement del = prepareStatement(getDeletePermissionsQuery()); 613 PreparedStatement ins = prepareStatement(getInsertPermissionUpdatingQuery())) { 614 del.setInt(1, segment.id()); 615 del.executeUpdate(); 616 617 if (permissions != null) { 618 ins.setInt(1, segment.id()); 619 for (Map.Entry<String, Integer> ent : permissions.entrySet()) { 620 ins.setString(2, ent.getKey()); 621 ins.setInt(3, ent.getValue()); 622 ins.addBatch(); 623 } 624 ins.executeBatch(); 625 } 626 } 627 return null; 628 }); 629 } 630 631 public void setDefaultValue(final SqlSegment segment, final @Nullable Integer permissionDefault) throws SQLException { 632 try (PreparedStatement stmt = prepareStatement(getUpdatePermissionDefaultQuery())) { 633 if (permissionDefault == null || permissionDefault == 0) { 634 stmt.setNull(1, Types.INTEGER); 635 } else { 636 stmt.setInt(1, permissionDefault); 637 } 638 stmt.setInt(2, segment.id()); 639 stmt.executeUpdate(); 640 } 641 } 642 643 public void addParent(SqlSegment seg, SqlSubjectRef<?> parent) throws SQLException { 644 try (PreparedStatement stmt = prepareStatement(getInsertInheritanceQuery())) { 645 stmt.setInt(1, seg.id()); 646 stmt.setInt(2, getIdAllocating(parent)); 647 stmt.executeUpdate(); 648 } 649 } 650 651 public void removeParent(SqlSegment segment, SqlSubjectRef<?> parent) throws SQLException { 652 try (PreparedStatement stmt = prepareStatement(getDeleteInheritanceParentQuery())) { 653 stmt.setInt(1, segment.id()); 654 stmt.setInt(2, getIdAllocating(parent)); 655 stmt.executeUpdate(); 656 } 657 } 658 659 public void setParents(SqlSegment segment, final @Nullable Iterable<SqlSubjectRef<?>> parents) throws SQLException { 660 executeInTransaction(() -> { 661 try (PreparedStatement del = prepareStatement(getDeleteInheritanceQuery()); 662 PreparedStatement ins = prepareStatement(getInsertInheritanceQuery())) { 663 del.setInt(1, segment.id()); 664 del.executeUpdate(); 665 666 if (parents != null) { 667 ins.setInt(1, segment.id()); 668 for (SqlSubjectRef<?> ent : parents) { 669 ins.setInt(2, getIdAllocating(ent)); 670 ins.addBatch(); 671 } 672 ins.executeBatch(); 673 } 674 } 675 return null; 676 }); 677 } 678 679 public SqlContextInheritance getContextInheritance() throws SQLException { 680 try (PreparedStatement stmt = prepareStatement(getSelectContextInheritanceQuery())) { 681 PMap<ContextValue<?>, PVector<ContextValue<?>>> ret = PCollections.map(); 682 @Nullable ContextValue<?> current = null; 683 PVector<ContextValue<?>> builder = PCollections.vector(); 684 ResultSet rs = stmt.executeQuery(); 685 while (rs.next()) { 686 final String childKey = rs.getString(1), 687 childValue = rs.getString(2), 688 parentKey = rs.getString(3), 689 parentValue = rs.getString(4); 690 if (current == null || !childKey.equals(current.key()) || !childValue.equals(current.rawValue())) { 691 if (current != null && !builder.isEmpty()) { 692 ret = ret.plus(current, builder); 693 } 694 current = new ContextValue<>(childKey, childValue); 695 builder = PCollections.vector(); 696 } 697 builder = builder.plus(new ContextValue<>(parentKey, parentValue)); 698 } 699 700 if (current != null) { 701 ret = ret.plus(current, builder); 702 } 703 704 return new SqlContextInheritance(ret); 705 } 706 707 } 708 709 public void setContextInheritance(final ContextValue<?> child, final @Nullable PVector<ContextValue<?>> parents) throws SQLException { 710 executeInTransaction(() -> { 711 try (PreparedStatement delete = prepareStatement(getDeleteContextInheritanceQuery()); 712 PreparedStatement insert = prepareStatement(getInsertContextInheritanceQuery())) { 713 delete.setString(1, child.key()); 714 delete.setString(2, child.rawValue()); 715 delete.executeUpdate(); 716 717 if (parents != null && parents.size() > 0) { 718 insert.setString(1, child.key()); 719 insert.setString(2, child.rawValue()); 720 for (ContextValue<?> parent : parents) { 721 insert.setString(3, parent.key()); 722 insert.setString(4, parent.rawValue()); 723 insert.addBatch(); 724 } 725 insert.executeBatch(); 726 } 727 } 728 return null; 729 }); 730 } 731 732 public RankLadder getRankLadder(String name) throws SQLException { 733 PVector<SubjectRef<?>> elements = PCollections.vector(); 734 try (PreparedStatement stmt = prepareStatement(getSelectRankLadderQuery())) { 735 stmt.setString(1, name); 736 ResultSet rs = stmt.executeQuery(); 737 while (rs.next()) { 738 elements = elements.plus(new SqlSubjectRef<>(this.ds.ctx(), rs.getInt(2), rs.getString(3), rs.getString(4))); 739 } 740 } 741 return new SqlRankLadder(name, elements); 742 } 743 744 public boolean hasEntriesForRankLadder(String name) throws SQLException { 745 try (PreparedStatement stmt = prepareStatement(getTestRankLadderExistsQuery())) { 746 stmt.setString(1, name); 747 return stmt.executeQuery().next(); 748 } 749 } 750 751 public void setRankLadder(String name, final @Nullable RankLadder ladder) throws SQLException { 752 executeInTransaction(() -> { 753 final List<? extends SubjectRef<?>> ranks; 754 if (ladder == null) { 755 ranks = PCollections.vector(); 756 } else { 757 ranks = ladder.ranks(); 758 } 759 760 try (PreparedStatement delete = prepareStatement(getDeleteRankLadderQuery()); 761 PreparedStatement insert = prepareStatement(getInsertRankLadderQuery())) { 762 delete.setString(1, name); 763 delete.executeUpdate(); 764 765 if (ladder != null) { 766 insert.setString(1, name); 767 for (final SubjectRef<?> plain : ranks) { 768 final SqlSubjectRef<?> ref = SqlSubjectRef.from(plain); 769 insert.setInt(2, getIdAllocating(ref)); 770 insert.addBatch(); 771 } 772 insert.executeBatch(); 773 } 774 } 775 return null; 776 }); 777 } 778 779 public Set<String> getAllRankLadderNames() throws SQLException { 780 try (PreparedStatement stmt = prepareStatement(getSelectAllRankLadderNamesQuery())) { 781 PSet<String> ret = PCollections.set(); 782 ResultSet rs = stmt.executeQuery(); 783 while (rs.next()) { 784 ret = ret.plus(rs.getString(1)); 785 } 786 return ret; 787 } 788 } 789 790 public Iterable<SqlSubjectRef<?>> getAllSubjectRefs() throws SQLException { 791 try (PreparedStatement stmt = prepareStatement(getSelectAllSubjectsQuery())) { 792 PSet<SqlSubjectRef<?>> ret = PCollections.set(); 793 ResultSet rs = stmt.executeQuery(); 794 while (rs.next()) { 795 ret = ret.plus(new SqlSubjectRef<>(this.ds.ctx(), rs.getInt(1), rs.getString(2), rs.getString(3))); 796 } 797 return ret; 798 } 799 } 800 801 public Set<String> getUsedContextKeys() throws SQLException { 802 try (PreparedStatement stmt = prepareStatement(getSelectAllContextKeysUniqueQuery())){ 803 PSet<String> builder = PCollections.set(); 804 ResultSet rs = stmt.executeQuery(); 805 while (rs.next()) { 806 builder = builder.plus(rs.getString(1)); 807 } 808 return builder; 809 } 810 } 811 812 @Override 813 public void close() throws SQLException { 814 if (this.holdOpen <= 0) { 815 this.conn.close(); 816 } 817 } 818 819 public void renameTable(String oldName, String newName) throws SQLException { 820 final String expandedOld = ds.getTableName(oldName); 821 final String expandedNew = ds.getTableName(newName); 822 try (PreparedStatement stmt = prepareStatement(getRenameTableQuery())) { 823 stmt.setString(1, expandedOld); 824 stmt.setString(2, expandedNew); 825 stmt.executeUpdate(); 826 } 827 } 828 829 public void deleteTable(String table) throws SQLException { 830 try (PreparedStatement stmt = prepareStatement("DROP TABLE " + ds.getTableName(table))) { 831 stmt.executeUpdate(); 832 } 833 } 834 835 /** 836 * Get the schema version. Has to include backwards compatibility to correctly handle pre-2.x schema updates. 837 * 838 * @return The schema version, 839 * @throws SQLException if unable to connect to database or perform query 840 */ 841 public int getSchemaVersion() throws SQLException { 842 if (hasTable("global")) { // Current 843 return getGlobalParameter(SqlConstants.OPTION_SCHEMA_VERSION).map(Integer::valueOf).orElse(SqlConstants.VERSION_PRE_VERSIONING); 844 } else if (legacy().hasTable(this, "permissions")) { // Legacy option 845 final @Nullable String ret = legacy().getOption(this, "system", LegacyMigration.Type.WORLD, null, "schema-version"); 846 return ret == null ? SqlConstants.VERSION_PRE_VERSIONING : Integer.parseInt(ret); 847 } else { 848 return SqlConstants.VERSION_NOT_INITIALIZED; 849 } 850 } 851 852 public void setSchemaVersion(int version) throws SQLException { 853 setGlobalParameter(SqlConstants.OPTION_SCHEMA_VERSION, Integer.toString(version)); 854 } 855 856 public SqlDataStore getDataStore() { 857 return ds; 858 } 859 860 public Connection getConnection() { 861 return conn; 862 } 863}