IndexKeyWriters.java

/*******************************************************************************
 * Copyright (c) 2025 Eclipse RDF4J contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 ******************************************************************************/
package org.eclipse.rdf4j.sail.lmdb.util;

import java.nio.ByteBuffer;

import org.eclipse.rdf4j.sail.lmdb.Varint;

public final class IndexKeyWriters {

	private static final int CACHE_SIZE = 1 << 12; // 4096 entries
	private static final int MASK = CACHE_SIZE - 1;

	private IndexKeyWriters() {
	}

	@FunctionalInterface
	public interface KeyWriter {
		void write(ByteBuffer bb, long subj, long pred, long obj, long context, boolean shouldCache);
	}

	@FunctionalInterface
	interface BasicWriter {
		void write(ByteBuffer bb, long subj, long pred, long obj, long context);
	}

	@FunctionalInterface
	public interface MatcherFactory {
		boolean[] create(long subj, long pred, long obj, long context);
	}

	public static KeyWriter forFieldSeq(String fieldSeq) {
		final BasicWriter basic;
		switch (fieldSeq) {
		case "spoc":
			basic = IndexKeyWriters::spoc;
			break;
		case "spco":
			basic = IndexKeyWriters::spco;
			break;
		case "sopc":
			basic = IndexKeyWriters::sopc;
			break;
		case "socp":
			basic = IndexKeyWriters::socp;
			break;
		case "scpo":
			basic = IndexKeyWriters::scpo;
			break;
		case "scop":
			basic = IndexKeyWriters::scop;
			break;
		case "psoc":
			basic = IndexKeyWriters::psoc;
			break;
		case "psco":
			basic = IndexKeyWriters::psco;
			break;
		case "posc":
			basic = IndexKeyWriters::posc;
			break;
		case "pocs":
			basic = IndexKeyWriters::pocs;
			break;
		case "pcso":
			basic = IndexKeyWriters::pcso;
			break;
		case "pcos":
			basic = IndexKeyWriters::pcos;
			break;
		case "ospc":
			basic = IndexKeyWriters::ospc;
			break;
		case "oscp":
			basic = IndexKeyWriters::oscp;
			break;
		case "opsc":
			basic = IndexKeyWriters::opsc;
			break;
		case "opcs":
			basic = IndexKeyWriters::opcs;
			break;
		case "ocsp":
			basic = IndexKeyWriters::ocsp;
			break;
		case "ocps":
			basic = IndexKeyWriters::ocps;
			break;
		case "cspo":
			basic = IndexKeyWriters::cspo;
			break;
		case "csop":
			basic = IndexKeyWriters::csop;
			break;
		case "cpso":
			basic = IndexKeyWriters::cpso;
			break;
		case "cpos":
			basic = IndexKeyWriters::cpos;
			break;
		case "cosp":
			basic = IndexKeyWriters::cosp;
			break;
		case "cops":
			basic = IndexKeyWriters::cops;
			break;
		default:
			throw new IllegalArgumentException("Unsupported field sequence: " + fieldSeq);
		}
		// Wrap the basic writer with a caching KeyWriter implementation
		return new CachingKeyWriter(basic);
	}

	// Simple array-based cache keyed by a masked index computed from a hashCode.
	private static final class CachingKeyWriter implements KeyWriter {

		private final CachingKeyWriter.Entry[] cache = new CachingKeyWriter.Entry[CACHE_SIZE];

		private static final class Entry {
			final long hashCode;
			final long s, p, o, c;
			final byte[] bytes;
			final int length;

			Entry(long hashCode, long s, long p, long o, long c, byte[] bytes) {
				this.hashCode = hashCode;
				this.s = s;
				this.p = p;
				this.o = o;
				this.c = c;
				this.bytes = bytes;
				this.length = bytes.length;
			}
		}

		private final BasicWriter basic;
		// Races are acceptable; we overwrite slots without synchronization.

		CachingKeyWriter(BasicWriter basic) {
			this.basic = basic;
		}

		@Override
		public void write(ByteBuffer bb, long subj, long pred, long obj, long context, boolean shouldCache) {
			if (!shouldCache) {
				basic.write(bb, subj, pred, obj, context);
				return;
			}

			long hashCode = subj - Long.MAX_VALUE + (pred - Long.MAX_VALUE) * 2 + (obj - Long.MAX_VALUE) * 3
					+ (context - Long.MAX_VALUE) * 4;
			int slot = (int) (hashCode & MASK);

			Entry e = cache[slot];

			if (e != null && e.hashCode == hashCode && e.s == subj && e.p == pred && e.o == obj && e.c == context) {
				bb.put(e.bytes, 0, e.length);
				return;
			}

			int len = Varint.calcListLengthUnsigned(subj, pred, obj, context);
			byte[] bytes = new byte[len];
			ByteBuffer out = ByteBuffer.wrap(bytes);
			basic.write(out, subj, pred, obj, context);
			out.flip();
			bb.put(out);
			cache[slot] = new Entry(hashCode, subj, pred, obj, context, bytes);
		}
	}

	public static MatcherFactory matcherFactory(String fieldSeq) {
		switch (fieldSeq) {
		case "spoc":
			return IndexKeyWriters::spocShouldMatch;
		case "spco":
			return IndexKeyWriters::spcoShouldMatch;
		case "sopc":
			return IndexKeyWriters::sopcShouldMatch;
		case "socp":
			return IndexKeyWriters::socpShouldMatch;
		case "scpo":
			return IndexKeyWriters::scpoShouldMatch;
		case "scop":
			return IndexKeyWriters::scopShouldMatch;
		case "psoc":
			return IndexKeyWriters::psocShouldMatch;
		case "psco":
			return IndexKeyWriters::pscoShouldMatch;
		case "posc":
			return IndexKeyWriters::poscShouldMatch;
		case "pocs":
			return IndexKeyWriters::pocsShouldMatch;
		case "pcso":
			return IndexKeyWriters::pcsoShouldMatch;
		case "pcos":
			return IndexKeyWriters::pcosShouldMatch;
		case "ospc":
			return IndexKeyWriters::ospcShouldMatch;
		case "oscp":
			return IndexKeyWriters::oscpShouldMatch;
		case "opsc":
			return IndexKeyWriters::opscShouldMatch;
		case "opcs":
			return IndexKeyWriters::opcsShouldMatch;
		case "ocsp":
			return IndexKeyWriters::ocspShouldMatch;
		case "ocps":
			return IndexKeyWriters::ocpsShouldMatch;
		case "cspo":
			return IndexKeyWriters::cspoShouldMatch;
		case "csop":
			return IndexKeyWriters::csopShouldMatch;
		case "cpso":
			return IndexKeyWriters::cpsoShouldMatch;
		case "cpos":
			return IndexKeyWriters::cposShouldMatch;
		case "cosp":
			return IndexKeyWriters::cospShouldMatch;
		case "cops":
			return IndexKeyWriters::copsShouldMatch;
		default:
			throw new IllegalArgumentException("Unsupported field sequence: " + fieldSeq);
		}
	}

	static void spoc(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, context);
	}

	static void spco(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, obj);
	}

	static void sopc(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, context);
	}

	static void socp(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, pred);
	}

	static void scpo(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, obj);
	}

	static void scop(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, pred);
	}

	static void psoc(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, context);
	}

	static void psco(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, obj);
	}

	static void posc(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, context);
	}

	static void pocs(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, subj);
	}

	static void pcso(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, obj);
	}

	static void pcos(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, subj);
	}

	static void ospc(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, context);
	}

	static void oscp(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, pred);
	}

	static void opsc(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, context);
	}

	static void opcs(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, subj);
	}

	static void ocsp(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, pred);
	}

	static void ocps(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, subj);
	}

	static void cspo(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, obj);
	}

	static void csop(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, pred);
	}

	static void cpso(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, obj);
	}

	static void cpos(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, subj);
	}

	static void cosp(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, subj);
		Varint.writeUnsigned(bb, pred);
	}

	static void cops(ByteBuffer bb, long subj, long pred, long obj, long context) {
		Varint.writeUnsigned(bb, context);
		Varint.writeUnsigned(bb, obj);
		Varint.writeUnsigned(bb, pred);
		Varint.writeUnsigned(bb, subj);
	}

	static boolean[] spocShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { subj > 0, pred > 0, obj > 0, context >= 0 };
	}

	static boolean[] spcoShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { subj > 0, pred > 0, context >= 0, obj > 0 };
	}

	static boolean[] sopcShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { subj > 0, obj > 0, pred > 0, context >= 0 };
	}

	static boolean[] socpShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { subj > 0, obj > 0, context >= 0, pred > 0 };
	}

	static boolean[] scpoShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { subj > 0, context >= 0, pred > 0, obj > 0 };
	}

	static boolean[] scopShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { subj > 0, context >= 0, obj > 0, pred > 0 };
	}

	static boolean[] psocShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { pred > 0, subj > 0, obj > 0, context >= 0 };
	}

	static boolean[] pscoShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { pred > 0, subj > 0, context >= 0, obj > 0 };
	}

	static boolean[] poscShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { pred > 0, obj > 0, subj > 0, context >= 0 };
	}

	static boolean[] pocsShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { pred > 0, obj > 0, context >= 0, subj > 0 };
	}

	static boolean[] pcsoShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { pred > 0, context >= 0, subj > 0, obj > 0 };
	}

	static boolean[] pcosShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { pred > 0, context >= 0, obj > 0, subj > 0 };
	}

	static boolean[] ospcShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { obj > 0, subj > 0, pred > 0, context >= 0 };
	}

	static boolean[] oscpShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { obj > 0, subj > 0, context >= 0, pred > 0 };
	}

	static boolean[] opscShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { obj > 0, pred > 0, subj > 0, context >= 0 };
	}

	static boolean[] opcsShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { obj > 0, pred > 0, context >= 0, subj > 0 };
	}

	static boolean[] ocspShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { obj > 0, context >= 0, subj > 0, pred > 0 };
	}

	static boolean[] ocpsShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { obj > 0, context >= 0, pred > 0, subj > 0 };
	}

	static boolean[] cspoShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { context >= 0, subj > 0, pred > 0, obj > 0 };
	}

	static boolean[] csopShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { context >= 0, subj > 0, obj > 0, pred > 0 };
	}

	static boolean[] cpsoShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { context >= 0, pred > 0, subj > 0, obj > 0 };
	}

	static boolean[] cposShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { context >= 0, pred > 0, obj > 0, subj > 0 };
	}

	static boolean[] cospShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { context >= 0, obj > 0, subj > 0, pred > 0 };
	}

	static boolean[] copsShouldMatch(long subj, long pred, long obj, long context) {
		return new boolean[] { context >= 0, obj > 0, pred > 0, subj > 0 };
	}
}